mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-07-15 21:15:23 +02:00
bug 209359 - CharArrayMap
This commit is contained in:
parent
6c009d8c5e
commit
faf69d0940
3 changed files with 604 additions and 0 deletions
|
@ -0,0 +1,339 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 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.cdt.core.parser.tests.ast2;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.eclipse.cdt.core.parser.util.CharArrayMap;
|
||||
import org.eclipse.cdt.core.parser.util.CharArrayObjectMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Mike Kucera
|
||||
*/
|
||||
public class CharArrayMapTest extends TestCase {
|
||||
|
||||
private static class Slice { // convenience class
|
||||
final char[] chars;
|
||||
final int start;
|
||||
final int length;
|
||||
public Slice(char[] chars, int start, int length) {
|
||||
this.chars = chars;
|
||||
this.length = length;
|
||||
this.start = start;
|
||||
}
|
||||
public String toString() {
|
||||
return new String(chars, start, length);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void disabled_testPerformance() {
|
||||
final int iterations = 10000;
|
||||
// insert tons of keys
|
||||
char[][] keys = new char[iterations][];
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
keys[i] = String.valueOf(i).toCharArray();
|
||||
}
|
||||
|
||||
System.gc();
|
||||
long mapTime = timeMap(keys);
|
||||
|
||||
System.gc();
|
||||
long oldMapTime = timeOldMap(keys);
|
||||
|
||||
System.out.println("mapTime: " + mapTime);
|
||||
System.out.println("oldMapTime: " + oldMapTime);
|
||||
assertTrue(oldMapTime > mapTime);
|
||||
}
|
||||
|
||||
|
||||
private static long timeMap(char[][] keys) {
|
||||
long start = System.currentTimeMillis();
|
||||
CharArrayMap/*<Integer>*/ map = new CharArrayMap/*<Integer>*/(keys.length);
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
map.put(keys[i], new Integer(i));
|
||||
}
|
||||
assertEquals(keys.length, map.size());
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
assertEquals(new Integer(i), map.get(keys[i]));
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
|
||||
private static long timeOldMap(char[][] keys) {
|
||||
long start = System.currentTimeMillis();
|
||||
CharArrayObjectMap oldMap = new CharArrayObjectMap(keys.length);
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
oldMap.put(keys[i], new Integer(i));
|
||||
}
|
||||
assertEquals(keys.length, oldMap.size());
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
assertEquals(new Integer(i), oldMap.get(keys[i]));
|
||||
}
|
||||
return System.currentTimeMillis() - start;
|
||||
}
|
||||
|
||||
|
||||
public void testBasicUsage1() {
|
||||
char[] key1 = "first key".toCharArray();
|
||||
char[] key2 = "second key".toCharArray();
|
||||
char[] key3 = "third key".toCharArray();
|
||||
char[] key4 = "forth key".toCharArray();
|
||||
|
||||
CharArrayMap/*<Integer>*/ map = new CharArrayMap/*<Integer>*/();
|
||||
assertTrue(map.isEmpty());
|
||||
assertEquals(0, map.size());
|
||||
|
||||
map.put(key1, new Integer(1));
|
||||
map.put(key2, new Integer(2));
|
||||
map.put(key3, new Integer(3));
|
||||
map.put(key4, new Integer(4));
|
||||
|
||||
assertFalse(map.isEmpty());
|
||||
assertEquals(4, map.size());
|
||||
|
||||
assertEquals(new Integer(1), map.get(key1));
|
||||
assertEquals(new Integer(2), map.get(key2));
|
||||
assertEquals(new Integer(3), map.get(key3));
|
||||
assertEquals(new Integer(4), map.get(key4));
|
||||
|
||||
assertTrue(map.containsKey(key1));
|
||||
assertTrue(map.containsKey(key2));
|
||||
assertTrue(map.containsKey(key3));
|
||||
assertTrue(map.containsKey(key4));
|
||||
|
||||
assertTrue(map.containsValue(new Integer(1)));
|
||||
assertTrue(map.containsValue(new Integer(2)));
|
||||
assertTrue(map.containsValue(new Integer(3)));
|
||||
assertTrue(map.containsValue(new Integer(4)));
|
||||
|
||||
Set/*<Integer>*/ values = new HashSet/*<Integer>*/();
|
||||
values.add(new Integer(1));
|
||||
values.add(new Integer(2));
|
||||
values.add(new Integer(3));
|
||||
values.add(new Integer(4));
|
||||
Collection c = map.values();
|
||||
for(Iterator iter = c.iterator(); iter.hasNext();) {
|
||||
assertTrue(values.remove(iter.next()));
|
||||
}
|
||||
|
||||
// remove a mapping
|
||||
assertEquals(new Integer(1), map.remove(key1));
|
||||
assertEquals(3, map.size());
|
||||
assertNull(map.get(key1));
|
||||
assertFalse(map.containsKey(key1));
|
||||
assertFalse(map.containsValue(new Integer(1)));
|
||||
assertNull(map.remove(key1)); // its already removed
|
||||
|
||||
map.clear();
|
||||
assertTrue(map.isEmpty());
|
||||
assertEquals(0, map.size());
|
||||
|
||||
// test null values
|
||||
map.put(key1, null);
|
||||
assertEquals(1, map.size());
|
||||
assertNull(map.get(key1));
|
||||
assertTrue(map.containsKey(key1));
|
||||
assertTrue(map.containsValue(null));
|
||||
|
||||
// overrideing values should
|
||||
map.put(key1, new Integer(100));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(new Integer(100), map.get(key1));
|
||||
assertTrue(map.containsValue(new Integer(100)));
|
||||
assertFalse(map.containsValue(null));
|
||||
// override the value
|
||||
map.put(key1, new Integer(200));
|
||||
assertEquals(1, map.size());
|
||||
assertEquals(new Integer(200), map.get(key1));
|
||||
assertTrue(map.containsValue(new Integer(200)));
|
||||
assertFalse(map.containsValue(new Integer(100)));
|
||||
}
|
||||
|
||||
|
||||
public void testBasicUsage2() {
|
||||
char[] chars = "pantera, megadeth, soulfly, metallica, in flames, lamb of god, carcass".toCharArray();
|
||||
|
||||
Slice[] slices = new Slice[7];
|
||||
slices[0] = new Slice(chars, 0, 7);
|
||||
slices[1] = new Slice(chars, 9, 8);
|
||||
slices[2] = new Slice(chars, 19, 7);
|
||||
slices[3] = new Slice(chars, 28, 9);
|
||||
slices[4] = new Slice(chars, 39, 9);
|
||||
slices[5] = new Slice(chars, 50, 11);
|
||||
slices[6] = new Slice(chars, 63, 7);
|
||||
|
||||
char[][] keys = new char[7][];
|
||||
keys[0] = "pantera".toCharArray();
|
||||
keys[1] = "megadeth".toCharArray();
|
||||
keys[2] = "soulfly".toCharArray();
|
||||
keys[3] = "metallica".toCharArray();
|
||||
keys[4] = "in flames".toCharArray();
|
||||
keys[5] = "lamb of god".toCharArray();
|
||||
keys[6] = "carcass".toCharArray();
|
||||
|
||||
|
||||
CharArrayMap/*<Integer>*/ map = new CharArrayMap/*<Integer>*/();
|
||||
assertTrue(map.isEmpty());
|
||||
assertEquals(0, map.size());
|
||||
|
||||
for(int i = 0; i < slices.length; i++) {
|
||||
Slice slice = slices[i];
|
||||
map.put(slice.chars, slice.start, slice.length, new Integer(i));
|
||||
}
|
||||
|
||||
assertFalse(map.isEmpty());
|
||||
assertEquals(7, map.size());
|
||||
|
||||
// should still work with equivalent keys
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
Slice slice = slices[i];
|
||||
assertEquals(new Integer(i), map.get(slice.chars, slice.start, slice.length));
|
||||
assertEquals(new Integer(i), map.get(keys[i]));
|
||||
assertTrue(map.containsKey(slice.chars, slice.start, slice.length));
|
||||
assertTrue(map.containsKey(keys[i]));
|
||||
assertTrue(map.containsValue(new Integer(i)));
|
||||
}
|
||||
|
||||
Set/*<Integer>*/ values = new HashSet/*<Integer>*/();
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
values.add(new Integer(i));
|
||||
}
|
||||
Collection c = map.values();
|
||||
for(Iterator iter = c.iterator(); iter.hasNext();) {
|
||||
assertTrue(values.remove(iter.next()));
|
||||
}
|
||||
|
||||
// remove the last two keys
|
||||
map.remove(keys[5]);
|
||||
map.remove(slices[6].chars, slices[6].start, slices[6].length);
|
||||
|
||||
assertEquals(5, map.size());
|
||||
|
||||
// remaining keys should still be there
|
||||
for(int i = 0; i < 5; i++) {
|
||||
Slice slice = slices[i];
|
||||
assertEquals(new Integer(i), map.get(slice.chars, slice.start, slice.length));
|
||||
assertEquals(new Integer(i), map.get(keys[i]));
|
||||
assertTrue(map.containsKey(slice.chars, slice.start, slice.length));
|
||||
assertTrue(map.containsKey(keys[i]));
|
||||
assertTrue(map.containsValue(new Integer(i)));
|
||||
}
|
||||
|
||||
map.clear();
|
||||
assertTrue(map.isEmpty());
|
||||
assertEquals(0, map.size());
|
||||
}
|
||||
|
||||
|
||||
public void testProperFail() {
|
||||
char[] hello = "hello".toCharArray();
|
||||
CharArrayMap/*<Integer>*/ map = new CharArrayMap/*<Integer>*/();
|
||||
Integer value = new Integer(9);
|
||||
|
||||
|
||||
try {
|
||||
map.put(null, value);
|
||||
fail();
|
||||
} catch(NullPointerException _) {}
|
||||
|
||||
try {
|
||||
map.put(hello, -1, 5, value);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.put(hello, 0, -1, value);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.put(hello, 0, 100, value);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
|
||||
try {
|
||||
map.get(null);
|
||||
fail();
|
||||
} catch(NullPointerException _) {}
|
||||
|
||||
try {
|
||||
map.get(hello, -1, 5);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.get(hello, 0, -1);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.get(hello, 0, 100);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
|
||||
try {
|
||||
map.remove(null);
|
||||
fail();
|
||||
} catch(NullPointerException _) {}
|
||||
|
||||
try {
|
||||
map.remove(hello, -1, 5);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.remove(hello, 0, -1);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.remove(hello, 0, 100);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
|
||||
try {
|
||||
map.containsKey(null);
|
||||
fail();
|
||||
} catch(NullPointerException _) {}
|
||||
|
||||
try {
|
||||
map.containsKey(hello, -1, 5);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.containsKey(hello, 0, -1);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
try {
|
||||
map.containsKey(hello, 0, 100);
|
||||
fail();
|
||||
} catch(IndexOutOfBoundsException _) {}
|
||||
|
||||
|
||||
try {
|
||||
new CharArrayMap/*<Integer>*/(-1);
|
||||
} catch(IllegalArgumentException _) {}
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ public class DOMParserTestSuite extends TestCase {
|
|||
suite.addTest(CommentTests.suite());
|
||||
suite.addTest(TaskParserTest.suite());
|
||||
suite.addTest( CompletionTestSuite.suite() );
|
||||
suite.addTestSuite( CharArrayMapTest.class );
|
||||
return suite;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,264 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 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.cdt.core.parser.util;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* A facade for a Map that allows char[] slices to be used as keys.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* TODO: implement compareTo() so that the map may be sorted
|
||||
*/
|
||||
private static final class Key {
|
||||
private final char[] buffer;
|
||||
private final int start;
|
||||
private 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;
|
||||
}
|
||||
|
||||
public boolean equals(Object x) {
|
||||
if(this == x)
|
||||
return true;
|
||||
if(!(x instanceof Key))
|
||||
return false;
|
||||
|
||||
Key k = (Key) x;
|
||||
if(length != k.length)
|
||||
return false;
|
||||
|
||||
for(int i = start, j = k.start; i < length; i++, j++) {
|
||||
if(buffer[i] != k.buffer[j]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
int result = 17;
|
||||
for(int i = start; i < start+length; i++) {
|
||||
result = 37 * result + (int)buffer[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "'" + new String(buffer, start, length) + "'@(" + start + "," + length + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Used to enforce preconditions. Note that the NPE thats thrown by
|
||||
* mutator methods is thrown from the Key constructor.
|
||||
*/
|
||||
private static void checkBoundaries(char[] chars, int start, int length) {
|
||||
if(start < 0)
|
||||
throw new IndexOutOfBoundsException("start must be non-negative, got: " + start);//$NON-NLS-1$
|
||||
if(length < 0)
|
||||
throw new IndexOutOfBoundsException("length must be non-negative, got: " + length);//$NON-NLS-1$
|
||||
if(start >= chars.length)
|
||||
throw new IndexOutOfBoundsException("start is out of bounds, got: " + start);//$NON-NLS-1$
|
||||
if(start + length > chars.length)
|
||||
throw new IndexOutOfBoundsException("end is out of bounds, got: " + (start+length));//$NON-NLS-1$
|
||||
}
|
||||
|
||||
|
||||
private final Map/*<Key,V>*/ map;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs an empty CharArrayMap with default initial capacity.
|
||||
*/
|
||||
public CharArrayMap() {
|
||||
map = new HashMap/*<Key,V>*/();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an empty CharArrayMap with the given initial capacity.
|
||||
* @throws IllegalArgumentException if the initial capacity is negative
|
||||
*/
|
||||
public CharArrayMap(int initialCapacity) {
|
||||
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 IllegalArgumentException if the boundaries specified by start and length are out of range
|
||||
*/
|
||||
public void put(char[] chars, int start, int length, /*V*/Object value) {
|
||||
checkBoundaries(chars, start, length);
|
||||
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*/Object value) {
|
||||
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 IllegalArgumentException if the boundaries specified by start and length are out of range
|
||||
*/
|
||||
public /*V*/Object get(char[] chars, int start, int length) {
|
||||
checkBoundaries(chars, start, length);
|
||||
return 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*/Object get(char[] chars) {
|
||||
return 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 IllegalArgumentException if the boundaries specified by start and length are out of range
|
||||
*/
|
||||
public /*V*/Object remove(char[] chars, int start, int length) {
|
||||
checkBoundaries(chars, start, length);
|
||||
return 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
|
||||
* @throws IllegalArgumentException if the boundaries specified by start and length are out of range
|
||||
*/
|
||||
public /*V*/Object remove(char[] chars) {
|
||||
return 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 IllegalArgumentException 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 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
|
||||
* @throws IllegalArgumentException if the boundaries specified by start and length are out of range
|
||||
*/
|
||||
public boolean containsKey(char[] chars) {
|
||||
return map.containsKey(new Key(chars));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given value is contained in the map.
|
||||
*/
|
||||
public boolean containsValue(/*V*/Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this in a foreach loop.
|
||||
*/
|
||||
public Collection/*<V>*/ values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all mappings from the map.
|
||||
*/
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the number of mappings.
|
||||
*/
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the map is empty.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a String representation of the map.
|
||||
*/
|
||||
public String toString() {
|
||||
return map.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Add table
Reference in a new issue