blob: eed0a5a7415dbfdc9d2c940a063ebcebb9bee65e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.core.nd.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* Provides functionality similar to a Map, with the feature that char arrays
* and sections of char arrays (known as slices) may be used as keys.
*
* This class is useful because small pieces of an existing large char[] buffer
* can be directly used as map keys. This avoids the need to create many String
* objects as would normally be needed as keys in a standard java.util.Map.
* Thus performance is improved in the CDT core.
*
* Most methods are overloaded with two versions, one that uses a
* section of a char[] as the key (a slice), and one that uses
* the entire char[] as the key.
*
* This class is intended as a replacement for CharArrayObjectMap.
*
* ex:
* char[] key = "one two three".toCharArray();
* map.put(key, 4, 3, new Integer(99));
* map.get(key, 4, 3); // returns 99
* map.get("two".toCharArray()); // returns 99
*
* @author Mike Kucera
*
* @param <V>
*/
public final class CharArrayMap<V> {
/**
* Wrapper class used as keys in the map. The purpose
* of this class is to provide implementations of
* equals() and hashCode() that operate on array slices.
*
* This class is private so it is assumed that the arguments
* passed to the constructor are legal.
*/
private static final class Key implements Comparable<Key>{
final char[] buffer;
final int start;
final int length;
public Key(char[] buffer, int start, int length) {
this.buffer = buffer;
this.length = length;
this.start = start;
}
/**
* @throws NullPointerException if buffer is null
*/
public Key(char[] buffer) {
this.buffer = buffer;
this.length = buffer.length; // throws NPE
this.start = 0;
}
@Override
public boolean equals(Object x) {
if(this == x)
return true;
if(!(x instanceof Key))
return false;
Key k = (Key) x;
if(this.length != k.length)
return false;
for(int i = this.start, j = k.start; i < this.length; i++, j++) {
if(this.buffer[i] != k.buffer[j]) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
int result = 17;
for(int i = this.start; i < this.start+this.length; i++) {
result = 37 * result + this.buffer[i];
}
return result;
}
@SuppressWarnings("nls")
@Override
public String toString() {
String slice = new String(this.buffer, this.start, this.length);
return "'" + slice + "'@(" + this.start + "," + this.length + ")";
}
@Override
public int compareTo(Key other) {
char[] b1 = this.buffer, b2 = other.buffer;
for(int i = this.start, j = other.start; i < b1.length && j < b2.length; i++, j++) {
if(b1[i] != b2[j])
return b1[i] < b2[j] ? -1 : 1;
}
return b1.length - b2.length;
}
}
/**
* Used to enforce preconditions.
* Note that the NPE thrown by mutator methods is thrown from the Key constructor.
*
* @throws IndexOutOfBoundsException if boundaries are wrong in any way
*/
private static void checkBoundaries(char[] chars, int start, int length) {
if(start < 0 || length < 0 || start >= chars.length || start + length > chars.length)
throw new IndexOutOfBoundsException("Buffer length: " + chars.length + //$NON-NLS-1$
", Start index: " + start + //$NON-NLS-1$
", Length: " + length); //$NON-NLS-1$
}
private final Map<Key,V> map;
/**
* Constructs an empty CharArrayMap with default initial capacity.
*/
public CharArrayMap() {
this.map = new HashMap<Key,V>();
}
/**
* Static factory method that constructs an empty CharArrayMap with default initial capacity,
* and the map will be kept in ascending key order.
*
* Characters are compared using a strictly numerical comparison; it is not locale-dependent.
*/
public static <V> CharArrayMap<V> createOrderedMap() {
// TreeMap does not have a constructor that takes an initial capacity
return new CharArrayMap<V>(new TreeMap<Key, V>());
}
private CharArrayMap(Map<Key, V> map) {
assert map != null;
this.map = map;
}
/**
* Constructs an empty CharArrayMap with the given initial capacity.
* @throws IllegalArgumentException if the initial capacity is negative
*/
public CharArrayMap(int initialCapacity) {
this.map = new HashMap<Key,V>(initialCapacity);
}
/**
* Creates a new mapping in this map, uses the given array slice as the key.
* If the map previously contained a mapping for this key, the old value is replaced.
* @throws NullPointerException if chars is null
* @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
*/
public void put(char[] chars, int start, int length, V value) {
checkBoundaries(chars, start, length);
this.map.put(new Key(chars, start, length), value);
}
/**
* Creates a new mapping in this map, uses all of the given array as the key.
* If the map previously contained a mapping for this key, the old value is replaced.
* @throws NullPointerException if chars is null
*/
public void put(char[] chars, V value) {
this.map.put(new Key(chars), value);
}
/**
* Returns the value to which the specified array slice is mapped in this map,
* or null if the map contains no mapping for this key.
* @throws NullPointerException if chars is null
* @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
*/
public V get(char[] chars, int start, int length) {
checkBoundaries(chars, start, length);
return this.map.get(new Key(chars, start, length));
}
/**
* Returns the value to which the specified array is mapped in this map,
* or null if the map contains no mapping for this key.
* @throws NullPointerException if chars is null
*/
public V get(char[] chars) {
return this.map.get(new Key(chars));
}
/**
* Removes the mapping for the given array slice if present.
* Returns the value object that corresponded to the key
* or null if the key was not in the map.
* @throws NullPointerException if chars is null
* @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
*/
public V remove(char[] chars, int start, int length) {
checkBoundaries(chars, start, length);
return this.map.remove(new Key(chars, start, length));
}
/**
* Removes the mapping for the given array if present.
* Returns the value object that corresponded to the key
* or null if the key was not in the map.
* @throws NullPointerException if chars is null
*/
public V remove(char[] chars) {
return this.map.remove(new Key(chars));
}
/**
* Returns true if the given key has a value associated with it in the map.
* @throws NullPointerException if chars is null
* @throws IndexOutOfBoundsException if the boundaries specified by start and length are out of range
*/
public boolean containsKey(char[] chars, int start, int length) {
checkBoundaries(chars, start, length);
return this.map.containsKey(new Key(chars, start, length));
}
/**
* Returns true if the given key has a value associated with it in the map.
* @throws NullPointerException if chars is null
*/
public boolean containsKey(char[] chars) {
return this.map.containsKey(new Key(chars));
}
/**
* Returns true if the given value is contained in the map.
*/
public boolean containsValue(V value) {
return this.map.containsValue(value);
}
/**
* Use this in a foreach loop.
*/
public Collection<V> values() {
return this.map.values();
}
/**
* Returns the keys stored in the map.
*/
public Collection<char[]> keys() {
Set<Key> keys= this.map.keySet();
ArrayList<char[]> r= new ArrayList<char[]>(keys.size());
for (Key key : keys) {
r.add(CharArrayUtils.extract(key.buffer, key.start, key.length));
}
return r;
}
/**
* Removes all mappings from the map.
*/
public void clear() {
this.map.clear();
}
/**
* Returns the number of mappings.
*/
public int size() {
return this.map.size();
}
/**
* Returns true if the map is empty.
*/
public boolean isEmpty() {
return this.map.isEmpty();
}
/**
* Returns a String representation of the map.
*/
@Override
public String toString() {
return this.map.toString();
}
}