From: Tuukka Lehtonen Date: Thu, 3 Nov 2016 20:33:39 +0000 (+0200) Subject: Merge commit '53059ca' X-Git-Tag: v1.25.0~63 X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=commitdiff_plain;h=c125a1755cc7c4a6241c3c5bf841c3db0ff2d658;hp=591f4572f18d20a08a797a8e5c4a8dfc1b3320c1 Merge commit '53059ca' Conflicts: bundles/org.simantics.scl.runtime/scl/Prelude.scl refs #6726 refs #6788 refs #6791 --- diff --git a/bundles/org.simantics.scl.runtime/scl/Prelude.scl b/bundles/org.simantics.scl.runtime/scl/Prelude.scl index 6966b4489..21658862d 100644 --- a/bundles/org.simantics.scl.runtime/scl/Prelude.scl +++ b/bundles/org.simantics.scl.runtime/scl/Prelude.scl @@ -1879,6 +1879,17 @@ maybeToList :: Maybe a -> [a] maybeToList (Just a) = [a] maybeToList _ = [] +""" +`takeWhile p l`, returns the longest prefix (possibly empty) of list `l` of elements that satisfy `p` +""" +takeWhile :: (a -> Boolean) -> [a] -> [a] +takeWhile f l = loop 0 + where + len = length l + loop i | i == len = l + | f (l!i) = loop (i+1) + | otherwise = take i l + partition :: (a -> Boolean) -> [a] -> ([a], [a]) partition p l = runProc do res1 = newArrayList diff --git a/bundles/org.simantics.tests.modelled/META-INF/MANIFEST.MF b/bundles/org.simantics.tests.modelled/META-INF/MANIFEST.MF index 662bca7b7..bce9a42cf 100644 --- a/bundles/org.simantics.tests.modelled/META-INF/MANIFEST.MF +++ b/bundles/org.simantics.tests.modelled/META-INF/MANIFEST.MF @@ -10,7 +10,11 @@ Require-Bundle: org.eclipse.core.runtime, org.simantics.tests.modelled.ontology, org.simantics.scl.osgi, org.simantics, - org.simantics.scl.compiler + org.simantics.scl.compiler, + org.junit, + org.simantics.db.testing, + org.simantics.modeling;bundle-version="1.1.1" Bundle-RequiredExecutionEnvironment: JavaSE-1.8 Bundle-ActivationPolicy: lazy -Export-Package: org.simantics.tests.modelled +Export-Package: org.simantics.tests.modelled, + org.simantics.tests.modelled.junit diff --git a/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSRunner.java b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSRunner.java new file mode 100644 index 000000000..6f9bda06a --- /dev/null +++ b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSRunner.java @@ -0,0 +1,57 @@ +package org.simantics.tests.modelled.junit; + +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.Result; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunListener; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.ParentRunner; + +public abstract class RuntimeSTSRunner extends ParentRunner { + + public RuntimeSTSRunner(Class testClass) throws Exception { + super(testClass); + } + + public abstract void initialize() throws Exception; + + public abstract void deinitialize() throws Exception; + + @Override + protected List getChildren() { + System.out.println("getting children"); + return RuntimeTestCollector.collectTests(); + } + + @Override + protected Description describeChild(Runner child) { + return child.getDescription(); + } + + @Override + public void run(RunNotifier notifier) { + notifier.addListener(new RunListener() { + + @Override + public void testRunFinished(Result result) throws Exception { + deinitialize(); + } + }); + super.run(notifier); + } + + @Override + protected void runChild(Runner child, RunNotifier notifier) { + Description desc = describeChild(child); + notifier.fireTestStarted(desc); + try { + child.run(notifier); + notifier.fireTestFinished(desc); + } catch (Throwable e) { + notifier.fireTestFailure(new Failure(desc, e)); + } + } +} diff --git a/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSSuiteRunner.java b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSSuiteRunner.java new file mode 100644 index 000000000..70325319d --- /dev/null +++ b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSSuiteRunner.java @@ -0,0 +1,54 @@ +package org.simantics.tests.modelled.junit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.simantics.scl.compiler.commands.CommandSession; +import org.simantics.scl.osgi.SCLOsgi; + +public class RuntimeSTSSuiteRunner extends RuntimeSTSRunner { + + private final List children = new ArrayList<>(); + private final String suiteName; + + RuntimeSTSSuiteRunner(String suiteName) throws Exception { + super(RuntimeSTSSuiteRunner.class); + this.suiteName = suiteName; + } + + @Override + protected String getName() { + return suiteName; + } + + @Override + public Description getDescription() { + Description description = Description.createSuiteDescription(getName(), getRunnerAnnotations()); + getChildren().forEach(child -> description.addChild(describeChild(child))); + return description; + } + + public void addChildren(Collection testChildren) { + CommandSession session = new CommandSession(SCLOsgi.MODULE_REPOSITORY, null); + testChildren.forEach(c -> c.setCommandSession(session)); + children.addAll(testChildren); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + protected List getChildren() { + return (List) children; + } + + @Override + public void initialize() { + } + + @Override + public void deinitialize() { + } + +} diff --git a/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSTestRunner.java b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSTestRunner.java new file mode 100644 index 000000000..a80b84e75 --- /dev/null +++ b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeSTSTestRunner.java @@ -0,0 +1,69 @@ +package org.simantics.tests.modelled.junit; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; + +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.simantics.scl.compiler.commands.CommandSession; +import org.simantics.scl.compiler.commands.TestScriptExecutor; +import org.simantics.scl.osgi.SCLOsgi; +import org.simantics.scl.runtime.SCLContext; +import org.simantics.scl.runtime.reporting.SCLReportingHandler; + +public class RuntimeSTSTestRunner extends Runner { + + private final String name; + private final String code; + private final Integer priority; + private CommandSession session; + + public RuntimeSTSTestRunner(String name, String code, Integer priority) { + this.name = name; + this.code = code; + this.priority = priority; + } + + public void setCommandSession(CommandSession session) { + this.session = session; + } + + @Override + public Description getDescription() { + return Description.createTestDescription(RuntimeSTSTestRunner.class, name); + } + + @Override + public void run(RunNotifier notifier) { + StringReader reade = new StringReader(code); + BufferedReader reader = new BufferedReader(reade); + + SCLContext context = SCLContext.getCurrent(); + SCLReportingHandler printer = (SCLReportingHandler)context.get(SCLReportingHandler.REPORTING_HANDLER); + SCLReportingHandler handler; + if(printer instanceof SCLReportingHandler) + handler = (SCLReportingHandler)printer; + else + handler = SCLReportingHandler.DEFAULT; + + try { + if (session == null) + session = new CommandSession(SCLOsgi.MODULE_REPOSITORY, handler); + new TestScriptExecutor(session, reader, handler).execute(); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + reader.close(); + } catch (IOException e) { + // Ignore + } + } + } + + public Integer getPriority() { + return priority; + } +} diff --git a/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeTestCollector.java b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeTestCollector.java new file mode 100644 index 000000000..81d1ef7e7 --- /dev/null +++ b/bundles/org.simantics.tests.modelled/src/org/simantics/tests/modelled/junit/RuntimeTestCollector.java @@ -0,0 +1,89 @@ +package org.simantics.tests.modelled.junit; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.runner.Runner; +import org.simantics.Simantics; +import org.simantics.databoard.Bindings; +import org.simantics.db.ReadGraph; +import org.simantics.db.Resource; +import org.simantics.db.common.request.ObjectsWithType; +import org.simantics.db.common.request.UniqueRead; +import org.simantics.db.exception.DatabaseException; +import org.simantics.layer0.Layer0; +import org.simantics.modeling.ModelingUtils; +import org.simantics.scl.runtime.tuple.Tuple0; +import org.simantics.tests.modelled.ontology.TestsResource; + +public class RuntimeTestCollector { + + + /** + * TODO: The idea of this class was to collect all the tests from shared libraries and construct + * JUnit tests out of them programmatically and then run them with JUnit to get results + * + */ + private static Collection collectTestsFromGraph() { + try { + Collection suitess = Simantics.getSession().syncRequest(new UniqueRead>() { + + @Override + public Collection perform(ReadGraph graph) throws DatabaseException { + + List sharedOntologies = Simantics.applySCL("Simantics/SharedOntologies", "getSharedOntologies", graph, Tuple0.INSTANCE); + + Set suites = new HashSet<>(); + TestsResource TESTS = TestsResource.getInstance(graph); + Layer0 L0 = Layer0.getInstance(graph); + for (Resource sharedOntology : sharedOntologies) { + List stsSuites = ModelingUtils.searchByType(graph, sharedOntology, TESTS.STSSuite); + for (Resource stsSuite : stsSuites) { + try { + String suiteName = graph.getURI(stsSuite); + + Collection tests = graph.syncRequest(new ObjectsWithType(stsSuite, L0.ConsistsOf, TESTS.STSTest)); + if (tests.isEmpty()) + continue; + + RuntimeSTSSuiteRunner suite = new RuntimeSTSSuiteRunner(suiteName); + List testRunners = new ArrayList<>(); + for (Resource test : tests) { + String testName = graph.getRelatedValue(test, L0.HasName, Bindings.STRING); + String code = graph.getRelatedValue(test, TESTS.STSTest_definition, Bindings.STRING); + Integer priority = graph.getPossibleRelatedValue(test, TESTS.STSTest_executionPriority, Bindings.INTEGER); + RuntimeSTSTestRunner testCase = new RuntimeSTSTestRunner(testName, code, priority); + testRunners.add(testCase); + } + + testRunners.sort((test1, test2) -> { + if (test1.getPriority() <= test2.getPriority()) + return -1; + else + return 1; + }); + suite.addChildren(testRunners); + suites.add(suite); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + return suites; + } + }); + return suitess; + } catch (DatabaseException e) { + e.printStackTrace(); + return Collections.emptyList(); + } + } + + public static List collectTests() { + return new ArrayList<>(collectTestsFromGraph()); + } +} diff --git a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMap.java b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMap.java index 7115f29d5..b9b5415d8 100644 --- a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMap.java +++ b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMap.java @@ -39,8 +39,8 @@ import java.util.Set; */ public class ArrayMap implements Map { - K[] keys; - V[] values; + final K[] keys; + final V[] values; Set> entrySet; Set keySet; Collection valueSet; @@ -292,7 +292,7 @@ public class ArrayMap implements Map { public Map.Entry next() { if (i >= keys.length) throw new NoSuchElementException("no more elements (" + keys.length + " walked)"); - Map.Entry entry = new Entry(i, keys[i], values[i]); + Map.Entry entry = new ArrayMapEntry(i, keys[i], values[i]); ++i; return entry; } @@ -306,63 +306,6 @@ public class ArrayMap implements Map { } - static class Entry implements Map.Entry { - final K key; - V value; - - /** - * Creates new entry. - */ - Entry(int h, K k, V v) { - value = v; - key = k; - } - - @Override - public final K getKey() { - return key; - } - - @Override - public final V getValue() { - return value; - } - - @Override - public final V setValue(V newValue) { - V oldValue = value; - value = newValue; - return oldValue; - } - - @Override - public final 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; - } - - @Override - public final int hashCode() { - return (key==null ? 0 : key.hashCode()) ^ - (value==null ? 0 : value.hashCode()); - } - - @Override - public final String toString() { - return getKey() + "=" + getValue(); - } - } - /** * Returns the hash code value for this map. The hash code of a map is * defined to be the sum of the hash codes of each entry in the map's diff --git a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMapEntry.java b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMapEntry.java new file mode 100644 index 000000000..4a7fd2e45 --- /dev/null +++ b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/ArrayMapEntry.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2007, 2016 Association for Decentralized Information Management + * in Industry THTH ry. + * 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.utils.datastructures; + +import java.util.Map; + +/** + * A common {@link Entry} implementation for both {@link ArrayMap} + * and {@link InterlacedArrayMap}. + * + * @author Tuukka Lehtonen + * + * @param key class + * @param value class + */ +class ArrayMapEntry implements Map.Entry { + final K key; + V value; + + /** + * Creates new entry. + */ + ArrayMapEntry(int h, K k, V v) { + value = v; + key = k; + } + + @Override + public final K getKey() { + return key; + } + + @Override + public final V getValue() { + return value; + } + + @Override + public final V setValue(V newValue) { + V oldValue = value; + value = newValue; + return oldValue; + } + + @Override + public final 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; + } + + @Override + public final int hashCode() { + return (key==null ? 0 : key.hashCode()) ^ + (value==null ? 0 : value.hashCode()); + } + + @Override + public final String toString() { + return getKey() + "=" + getValue(); + } +} \ No newline at end of file diff --git a/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/InterlacedArrayMap.java b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/InterlacedArrayMap.java new file mode 100644 index 000000000..6ea16dd52 --- /dev/null +++ b/bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/InterlacedArrayMap.java @@ -0,0 +1,538 @@ +/******************************************************************************* + * Copyright (c) 2016 Association for Decentralized Information Management + * in Industry THTH ry. + * 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: + * Semantum Oy - initial API and implementation + *******************************************************************************/ +package org.simantics.utils.datastructures; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +/** + * A space-optimized immutable Map implementation where keys and values are + * interlaced in one Object array. + * + *

+ * Both map keys and values are specified as separate typed arrays that must be + * equally sized. This provides for space-optimization by allowing reusable key + * array instances since the keys cannot be modified. + * + *

+ * The map should be considered immutable, but it does allow modification of a + * value for an existing key as this only changes the value, the key will remain + * untouched. + * + * The nested class {@link InterlacedArrayMapBuilder} allows easy and + * minimal-allocation construction of an {@link InterlacedArrayMap}. Use + * {@link InterlacedArrayMap#builder()} or + * {@link InterlacedArrayMap#builder(int)} to create the builder. The idea is to + * minimize the space allocated for the {@link #data} field in the built map + * instance. If the builder is completely filled up, i.e. its capacity matches + * added key, value pair count, the array it contains is directly transferred to + * the built map instance without creating any extra copies. So if possible, set + * the correct initial capacity for the builder. + * + * @author Tuukka Lehtonen + * + * @param + * key class + * @param + * value class + */ +public class InterlacedArrayMap implements Map { + + final Object[] data; + Set> entrySet; + Set keySet; + Collection valueSet; + + public static class InterlacedArrayMapBuilder { + + private Object[] elementData; + private int count = 0; + + private InterlacedArrayMapBuilder(int initialCapacity) { + this.elementData = new Object[initialCapacity*2]; + } + + public void add(K key, V v) { + ensureCapacityInternal(count + 2); + elementData[count] = key; + elementData[count+1] = v; + count += 2; + } + + private void ensureCapacityInternal(int minCapacity) { + // overflow-conscious code + if (minCapacity - elementData.length > 0) + grow(minCapacity); + } + + /** + * The maximum size of array to allocate. + * Some VMs reserve some header words in an array. + * Attempts to allocate larger arrays may result in + * OutOfMemoryError: Requested array size exceeds VM limit + */ + private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * Increases the capacity to ensure that it can hold at least the + * number of elements specified by the minimum capacity argument. + * + * @param minCapacity the desired minimum capacity + */ + private void grow(int minCapacity) { + // overflow-conscious code + int oldCapacity = elementData.length; + int newCapacity = oldCapacity + (oldCapacity >> 1); + if (newCapacity - minCapacity < 0) + newCapacity = minCapacity; + if (newCapacity - MAX_ARRAY_SIZE > 0) + newCapacity = hugeCapacity(minCapacity); + // minCapacity is usually close to size, so this is a win: + elementData = Arrays.copyOf(elementData, newCapacity); + } + + private static int hugeCapacity(int minCapacity) { + if (minCapacity < 0) // overflow + throw new OutOfMemoryError(); + return (minCapacity > MAX_ARRAY_SIZE) ? + Integer.MAX_VALUE : + MAX_ARRAY_SIZE; + } + + public InterlacedArrayMap build() { + return count == elementData.length + ? new InterlacedArrayMap<>(elementData) + : new InterlacedArrayMap<>(Arrays.copyOf(elementData, count)); + } + + } + + /** + * Default initial capacity. + */ + private static final int DEFAULT_CAPACITY = 10; + + /** + * @return a map builder with the default initial capacity of + * {@value #DEFAULT_CAPACITY} key,value pairs. + */ + public static InterlacedArrayMapBuilder builder() { + return new InterlacedArrayMapBuilder(DEFAULT_CAPACITY); + } + + /** + * @param initialCapacity + * the amount of key,value pairs to fit into the builder + * initially without having to reallocate anything + * @return a map builder with the specified initial key,value pair capacity + */ + public static InterlacedArrayMapBuilder builder(int initialCapacity) { + return new InterlacedArrayMapBuilder(initialCapacity); + } + + /** + * Just like {@link #InterlacedArrayMap(Object[])} but takes the array of + * objects as a collection. + * + * @param keysAndValues + * an interlaced array where where (key, value) pairs are + * repeated in consecutive indexes of the array + * @throws IllegalArgumentException + * if the array size is not divisible by two + * @throws NullPointerException + * if either parameter is null + */ + public InterlacedArrayMap(List keysAndValues) { + this(keysAndValues.toArray()); + } + + /** + * Constructs new ArrayMap based on an interlaced array of keys and values. + * The array size must be divisible by two. + * + * @param keysAndValues + * an interlaced array where where (key, value) pairs are + * repeated in consecutive indexes of the array + * @throws IllegalArgumentException + * if the array size is not divisible by two + * @throws NullPointerException + * if either parameter is null + */ + public InterlacedArrayMap(Object[] keysAndValues) { + // NPE is caught by this. + if ((keysAndValues.length & 1) != 0) + throw new IllegalArgumentException("key and value array size (" + keysAndValues.length + ") is not divisible by 2"); + this.data = keysAndValues; + } + + @Override + public Set> entrySet() { + return (entrySet != null) ? entrySet : (entrySet = new EntrySet()); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean containsKey(Object key) { + return contains(0, key); + } + + @Override + public boolean containsValue(Object value) { + return contains(1, value); + } + + @SuppressWarnings("unchecked") + @Override + public V get(Object key) { + if (key == null) { + for (int i = 0; i < data.length; i += 2) { + if (data[i] == null) { + return (V) data[i+1]; + } + } + return null; + } + int hash = key.hashCode(); + for (int i = 0; i < data.length; i += 2) { + Object k = data[i]; + if (k == key || (hash == k.hashCode() && key.equals(k))) { + return (V) data[i+1]; + } + } + return null; + } + + @Override + public boolean isEmpty() { + return data.length == 0; + } + + @Override + public Set keySet() { + return (keySet != null) ? keySet : (keySet = new OffsetSet(0)); + } + + @SuppressWarnings("unchecked") + @Override + public V put(K key, V value) { + if (key == null) { + for (int i = 0; i < data.length; i += 2) { + if (data[i] == null) { + V old = (V) data[i+1]; + data[i+1] = value; + return old; + } + } + throw new UnsupportedOperationException("key " + key + " not present in ArrayMap"); + } + int hash = key.hashCode(); + for (int i = 0; i < data.length; i += 2) { + K k = (K) data[i]; + if (k == key || (hash == k.hashCode() && key.equals(k))) { + V old = (V) data[i+1]; + data[i+1] = value; + return old; + } + } + throw new UnsupportedOperationException("key " + key + " not present in ArrayMap"); + } + + @Override + public void putAll(Map m) { + for (K k : m.keySet()) { + if (!containsKey(k)) + throw new UnsupportedOperationException("key " + k + " not present in ArrayMap"); + } + for (Map.Entry entry : m.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public int size() { + return data.length >> 1; + } + + @Override + public Collection values() { + return valueSet != null ? valueSet : (valueSet = new OffsetSet(1)); + } + + private boolean contains(int offset, Object o) { + int len = data.length; + if (o == null) { + for (int i = offset; i < len; i += 2) + if (data[i] == null) + return true; + return false; + } + int hash = o.hashCode(); + for (int i = offset; i < len; i += 2) { + Object k = data[i]; + if (o == k || (hash == k.hashCode() && o.equals(k))) + return true; + } + return false; + } + + class OffsetSet extends ImmutableSet implements Set { + + private final int offset; + + public OffsetSet(int offset) { + this.offset = offset; + } + + @Override + public boolean contains(Object o) { + return InterlacedArrayMap.this.contains(offset, o); + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) + if (!contains(o)) + return false; + return true; + } + + @Override + public boolean isEmpty() { + return data.length == 0; + } + + @Override + public Iterator iterator() { + return new ImmutableIterator() { + int i = offset; + + @Override + public boolean hasNext() { + return i < data.length; + } + + @Override + public T next() { + if (i >= data.length) + throw new NoSuchElementException("no more elements (" + data.length + " walked)"); + @SuppressWarnings("unchecked") + T t = (T) data[i]; + ++i; + return t; + } + }; + } + + @Override + public int size() { + return data.length >> 1; + } + + @Override + public Object[] toArray() { + int len = data.length >> 1; + Object[] a = new Object[len]; + for (int i = 0; i < len; ++i) + a[i] = data[i*2+offset]; + return a; + } + + @SuppressWarnings("unchecked") + @Override + public TT[] toArray(TT[] a) { + int len = data.length >> 1; + if (a.length < len) { + Class clazz = a.getClass(); + // Make a new array of a's runtime type, but my contents: + a = ((Object)clazz == (Object)Object[].class) + ? (TT[]) new Object[len] + : (TT[]) Array.newInstance(clazz.getComponentType(), len); + } + for (int i = 0; i < len; ++i) + a[i] = (TT) data[i*2+offset]; + if (a.length > len) + a[len] = null; + return a; + } + } + + class EntrySet extends ImmutableSet> implements Set> { + + @Override + public boolean contains(Object o) { + throw new UnsupportedOperationException("TODO"); + } + + @Override + public boolean containsAll(Collection c) { + for (Object o : c) + if (!contains(o)) + return false; + return true; + } + + @Override + public boolean isEmpty() { + return data.length == 0; + } + + @Override + public Iterator> iterator() { + return new ImmutableIterator>() { + int i = 0; + + @Override + public boolean hasNext() { + return i < data.length; + } + + @Override + public Map.Entry next() { + if (i >= data.length) + throw new NoSuchElementException("no more elements (" + (data.length >> 1) + " walked)"); + @SuppressWarnings("unchecked") + Map.Entry entry = new ArrayMapEntry(i, (K) data[i], (V) data[i+1]); + i += 2; + return entry; + } + }; + } + + @Override + public int size() { + return data.length >> 1; + } + + } + + /** + * Returns the hash code value for this map. The hash code of a map is + * defined to be the sum of the hash codes of each entry in the map's + * entrySet() view. This ensures that m1.equals(m2) + * implies that m1.hashCode()==m2.hashCode() for any two maps + * m1 and m2, as required by the general contract of + * {@link Object#hashCode}. + * + *

This implementation iterates over entrySet(), calling + * {@link Map.Entry#hashCode hashCode()} on each element (entry) in the + * set, and adding up the results. + * + * @return the hash code value for this map + * @see Map.Entry#hashCode() + * @see Object#equals(Object) + * @see Set#equals(Object) + */ + @Override + @SuppressWarnings("unchecked") + public int hashCode() { + int h = 0; + int l = data.length; + for (int i = 0; i < l; i += 2) { + K key = (K) data[i]; + V value = (V) data[i+1]; + int hash = (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); + h += hash; + } + return h; + } + + /** + * Compares the specified object with this map for equality. Returns + * true if the given object is also a map and the two maps + * represent the same mappings. More formally, two maps m1 and + * m2 represent the same mappings if + * m1.entrySet().equals(m2.entrySet()). This ensures that the + * equals method works properly across different implementations + * of the Map interface. + * + *

This implementation first checks if the specified object is this map; + * if so it returns true. Then, it checks if the specified + * object is a map whose size is identical to the size of this map; if + * not, it returns false. If so, it iterates over this map's + * entrySet collection, and checks that the specified map + * contains each mapping that this map contains. If the specified map + * fails to contain such a mapping, false is returned. If the + * iteration completes, true is returned. + * + * @param o object to be compared for equality with this map + * @return true if the specified object is equal to this map + */ + @Override + @SuppressWarnings("unchecked") + public boolean equals(Object o) { + if (o == this) + return true; + + if (!(o instanceof Map)) + return false; + Map m = (Map) o; + if (m.size() != size()) + return false; + + try { + int l = data.length; + for (int i = 0; i < l; i += 2) { + K key = (K) data[i]; + V value = (V) data[i+1]; + if (value == null) { + if (!(m.get(key)==null && m.containsKey(key))) + return false; + } else { + if (!value.equals(m.get(key))) + return false; + } + } + } catch (ClassCastException unused) { + return false; + } catch (NullPointerException unused) { + return false; + } + + return true; + } + + @Override + public String toString() { + Iterator> i = entrySet().iterator(); + if (! i.hasNext()) + return "{}"; + + StringBuilder sb = new StringBuilder(); + sb.append('{'); + for (;;) { + Map.Entry e = i.next(); + K key = e.getKey(); + V value = e.getValue(); + sb.append(key == this ? "(this Map)" : key); + sb.append('='); + sb.append(value == this ? "(this Map)" : value); + if (! i.hasNext()) + return sb.append('}').toString(); + sb.append(", "); + } + } +}