/*
* ====================================================================
* Copyright (c) 2004-2012 TMate Software Ltd. All rights reserved.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* are also available at http://svnkit.com/license.html.
* If newer versions of this license are posted there, you may use a
* newer version instead, at your option.
* ====================================================================
*/
package org.tmatesoft.svn.core.internal.util;
import java.io.File;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractCollection;
import java.util.AbstractSet;
import java.util.Arrays;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* @version 1.3
* @author TMate Software Ltd.
*/
public class SVNHashMap implements Map, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
private static final Object NULL_KEY = new Object();
private static final int INITIAL_CAPACITY = 16;
private static boolean ourIsCompatibilityMode = Boolean.getBoolean("svnkit.compatibleHash");
private transient TableEntry[] myTable;
private transient int myEntryCount;
private transient int myModCount;
private transient volatile Set myKeySet;
private transient volatile Set myEntrySet;
private transient volatile Collection myValueCollection;
public SVNHashMap() {
this(null);
}
public SVNHashMap(Map map) {
init();
putAll(map);
}
protected void init() {
myTable = new TableEntry[INITIAL_CAPACITY];
myEntryCount = 0;
}
public void clear() {
Arrays.fill(myTable, null);
myEntryCount = 0;
myModCount++;
}
public boolean isEmpty() {
return myEntryCount == 0;
}
public boolean containsKey(Object key) {
if (isEmpty()) {
return false;
}
key = key == null ? NULL_KEY : key;
int hash = hashCode(key);
int index = indexForHash(hash);
TableEntry entry = myTable[index];
while (entry != null) {
if (entry.hash == hash && eq(key, entry.key)) {
return true;
}
entry = entry.next;
}
return false;
}
public boolean containsValue(Object value) {
if (isEmpty()) {
return false;
}
if (value == null) {
return containsNullValue();
}
for (int i = 0; i < myTable.length; i++) {
TableEntry entry = myTable[i];
while (entry != null) {
if (value.equals(entry.getValue())) {
return true;
}
entry = entry.next;
}
}
return false;
}
private boolean containsNullValue() {
for (int i = 0; i < myTable.length; i++) {
TableEntry entry = myTable[i];
while (entry != null) {
if (entry.getValue() == null) {
return true;
}
entry = entry.next;
}
}
return false;
}
public Object get(Object key) {
key = key == null ? NULL_KEY : key;
int hash = hashCode(key);
int index = indexForHash(hash);
TableEntry entry = myTable[index];
while (entry != null) {
if (hash == entry.hash && eq(key, entry.key)) {
return entry.getValue();
}
entry = entry.next;
}
return null;
}
public int size() {
return myEntryCount;
}
public Object put(Object key, Object value) {
key = key == null ? NULL_KEY : key;
int hash = hashCode(key);
int index = indexForHash(hash);
TableEntry entry = myTable[index];
TableEntry previousEntry = null;
while (entry != null) {
if (entry.hash == hash && entry.key.equals(key)) {
myModCount++;
return entry.setValue(value);
}
previousEntry = entry;
entry = entry.next;
}
TableEntry newEntry = createTableEntry(key, value, hash);
if (previousEntry != null) {
previousEntry.next = newEntry;
} else {
myTable[index] = newEntry;
}
myEntryCount++;
myModCount++;
if (myEntryCount >= myTable.length) {
resize(myTable.length * 2);
}
return null;
}
protected TableEntry createTableEntry(Object key, Object value, int hash) {
return new TableEntry(key, value, hash);
}
public Object remove(Object key) {
if (isEmpty()) {
return null;
}
key = key == null ? NULL_KEY : key;
int hash = hashCode(key);
int index = indexForHash(hash);
TableEntry entry = myTable[index];
TableEntry previousEntry = null;
while (entry != null) {
if (entry.hash == hash && entry.key.equals(key)) {
if (previousEntry != null) {
previousEntry.next = entry.next;
} else {
myTable[index] = entry.next;
}
myEntryCount--;
myModCount++;
return entry.getValue();
}
previousEntry = entry;
entry = entry.next;
}
return null;
}
public void putAll(Map t) {
if (t == null || t.isEmpty()) {
return;
}
if (myEntryCount + t.size() >= myTable.length) {
resize((myEntryCount + t.size())*2);
}
for (Iterator entries = t.entrySet().iterator(); entries.hasNext();) {
Map.Entry entry = (Map.Entry) entries.next();
put(entry.getKey(), entry.getValue());
}
}
public Set keySet() {
if (myKeySet == null) {
myKeySet = new KeySet();
}
return myKeySet;
}
public Set entrySet() {
if (myEntrySet == null) {
myEntrySet = new EntrySet();
}
return myEntrySet;
}
public Collection values() {
if (myValueCollection == null) {
myValueCollection = new ValueCollection();
}
return myValueCollection;
}
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Map)) {
return false;
}
Map t = (Map) o;
if (t.size() != size()) {
return false;
}
try {
Iterator i = entrySet().iterator();
while (i.hasNext()) {
Map.Entry e = (Map.Entry) i.next();
Object key = e.getKey();
Object value = e.getValue();
if (value == null) {
if (!(t.get(key) == null && t.containsKey(key))) {
return false;
}
} else {
if (!value.equals(t.get(key))) {
return false;
}
}
}
} catch(ClassCastException unused) {
return false;
} catch(NullPointerException unused) {
return false;
}
return true;
}
public int hashCode() {
int h = 0;
Iterator i = entrySet().iterator();
while (i.hasNext()) {
h += i.next().hashCode();
}
return h;
}
public Object clone() throws CloneNotSupportedException {
try {
super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
SVNHashMap result = new SVNHashMap();
result.myTable = new TableEntry[myTable.length];
result.myEntryCount = myEntryCount;
result.myModCount = myModCount;
result.putAll(this);
return result;
}
private void writeObject(ObjectOutputStream s) throws IOException {
Iterator i = (myEntryCount > 0) ? entrySet().iterator() : null;
s.defaultWriteObject();
s.writeInt(myTable.length);
s.writeInt(myEntryCount);
if ( i == null) {
return;
}
while (i.hasNext()) {
Map.Entry e = (Map.Entry) i.next();
s.writeObject(e.getKey());
s.writeObject(e.getValue());
}
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
int numBuckets = s.readInt();
myTable = new TableEntry[numBuckets];
// Read in size (number of Mappings)
int size = s.readInt();
// Read the keys and values, and put the mappings in the HashMap
for (int i=0; i < size; i++) {
Object key = s.readObject();
Object value = s.readObject();
put(key, value);
}
}
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("{");
Iterator i = entrySet().iterator();
boolean hasNext = i.hasNext();
while (hasNext) {
Map.Entry e = (Map.Entry) (i.next());
Object key = e.getKey();
Object value = e.getValue();
buf.append(key == this ? "(this Map)" : key);
buf.append("=");buf.append(value == this ? "(this Map)" : value);
hasNext = i.hasNext();
if (hasNext) {
buf.append(", ");
}
}
buf.append("}");
return buf.toString();
}
private int indexForHash(int hash) {
return (myTable.length - 1) & hash;
}
private static int hashCode(Object key) {
if (ourIsCompatibilityMode && String.class == key.getClass()) {
int hash = 0;
String str = (String) key;
for (int i = 0; i < str.length(); i++) {
hash = hash*33 + str.charAt(i);
}
return hash;
} else if (key.getClass() == File.class) {
return hashCode(((File) key).getPath());
}
return key.hashCode();
}
private void resize(int newSize) {
TableEntry[] oldTable = myTable;
myTable = new TableEntry[newSize];
for (int i = 0; i < oldTable.length; i++) {
TableEntry oldEntry = oldTable[i];
while (oldEntry != null) {
int index = indexForHash(oldEntry.hash);
TableEntry newEntry = myTable[index];
if (newEntry == null) {
myTable[index] = oldEntry;
} else {
while (newEntry.next != null) {
newEntry = newEntry.next;
}
newEntry.next = oldEntry;
}
TableEntry nextEntry = oldEntry.next;
oldEntry.next = null;
oldEntry = nextEntry;
}
}
}
private static boolean eq(Object a, Object b) {
return a == b || a.equals(b);
}
private class KeySet extends AbstractSet {
public Iterator iterator() {
return new KeyIterator();
}
public int size() {
return myEntryCount;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return SVNHashMap.this.remove(o) != null;
}
public void clear() {
SVNHashMap.this.clear();
}
}
private class EntrySet extends AbstractSet {
public Iterator iterator() {
return new TableIterator();
}
public int size() {
return myEntryCount;
}
public boolean contains(Object o) {
if (o instanceof Map.Entry) {
Map.Entry entry = (Map.Entry) o;
if (SVNHashMap.this.containsKey(entry.getKey())) {
Object value = SVNHashMap.this.get(entry.getKey());
if (value == null) {
return entry.getValue() == null;
}
return value.equals(entry.getValue());
}
}
return false;
}
public boolean remove(Object o) {
if (contains(o)) {
Map.Entry entry = (Map.Entry) o;
return SVNHashMap.this.remove(entry.getKey()) != null;
}
return false;
}
public void clear() {
SVNHashMap.this.clear();
}
}
private class ValueCollection extends AbstractCollection {
public Iterator iterator() {
return new ValueIterator();
}
public int size() {
return myEntryCount;
}
public boolean contains(Object o) {
return containsValue(o);
}
public void clear() {
SVNHashMap.this.clear();
}
}
private class TableIterator implements Iterator {
private int index;
private TableEntry entry;
private TableEntry previous;
private int modCount;
public TableIterator() {
index = 0;
entry = null;
modCount = myModCount;
while (index < myTable.length && entry == null) {
entry = myTable[index];
index++;
}
}
public boolean hasNext() {
return entry != null;
}
public Object next() {
if (myModCount != modCount) {
throw new ConcurrentModificationException();
}
if (entry == null) {
throw new NoSuchElementException();
}
previous = entry;
entry = entry.next;
while (entry == null && index < myTable.length) {
entry = myTable[index];
index++;
}
return previous;
}
public void remove() {
if (myModCount != modCount) {
throw new ConcurrentModificationException();
}
if (previous != null) {
SVNHashMap.this.remove(previous.getKey());
previous = null;
modCount = myModCount;
} else {
throw new IllegalStateException();
}
}
}
private class KeyIterator extends TableIterator {
public Object next() {
TableEntry next = (TableEntry) super.next();
return next.getKey();
}
}
private class ValueIterator extends TableIterator {
public Object next() {
TableEntry next = (TableEntry) super.next();
return next.getValue();
}
}
protected static class TableEntry implements Map.Entry {
private TableEntry next;
private Object key;
private Object value;
private int hash;
protected TableEntry() {
}
public TableEntry(Object key, Object value, int hash) {
init(key, value, hash);
}
protected void init(Object key, Object value, int hash) {
this.key = key;
setValue(value);
this.hash = hash;
}
public Object setValue(Object value) {
Object oldValue = getValue();
this.value = value;
return oldValue;
}
public Object getValue() {
return value;
}
public Object getKey() {
return key == NULL_KEY ? null : key;
}
public int hashCode() {
return (key == NULL_KEY ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
}
public boolean equals(Object o) {
if (!(o instanceof Map.Entry)) {
return false;
}
Map.Entry e = (Map.Entry) o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2))) {
return true;
}
}
return false;
}
}
}