diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/CharArrayMapTest.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/CharArrayMapTest.java new file mode 100644 index 00000000000..80ea079196d --- /dev/null +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/CharArrayMapTest.java @@ -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/**/ map = new CharArrayMap/**/(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/**/ map = new CharArrayMap/**/(); + 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/**/ values = new HashSet/**/(); + 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/**/ map = new CharArrayMap/**/(); + 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/**/ values = new HashSet/**/(); + 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/**/ map = new CharArrayMap/**/(); + 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/**/(-1); + } catch(IllegalArgumentException _) {} + } +} diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/DOMParserTestSuite.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/DOMParserTestSuite.java index 5dc60ec2650..e48af9b0314 100644 --- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/DOMParserTestSuite.java +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/ast2/DOMParserTestSuite.java @@ -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; } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/util/CharArrayMap.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/util/CharArrayMap.java new file mode 100644 index 00000000000..373e87ad3c7 --- /dev/null +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/util/CharArrayMap.java @@ -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/**/ { + + /** + * 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/**/ map; + + + /** + * Constructs an empty CharArrayMap with default initial capacity. + */ + public CharArrayMap() { + map = new HashMap/**/(); + } + + /** + * Constructs an empty CharArrayMap with the given initial capacity. + * @throws IllegalArgumentException if the initial capacity is negative + */ + public CharArrayMap(int initialCapacity) { + map = new HashMap/**/(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/**/ 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(); + } + +} +