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