diff --git a/src/main/java/net/openhft/chronicle/hash/impl/util/CharSequences.java b/src/main/java/net/openhft/chronicle/hash/impl/util/CharSequences.java new file mode 100644 index 000000000..61bf8ccfa --- /dev/null +++ b/src/main/java/net/openhft/chronicle/hash/impl/util/CharSequences.java @@ -0,0 +1,51 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openhft.chronicle.hash.impl.util; + +import org.jetbrains.annotations.NotNull; + +public final class CharSequences { + + public static boolean equivalent(@NotNull CharSequence a, @NotNull CharSequence b) { + if (a.equals(b)) + return true; + if (a instanceof String) + return ((String) a).contentEquals(b); + if (b instanceof String) + return ((String) b).contentEquals(a); + int len = a.length(); + if (len != b.length()) + return false; + for (int i = 0; i < len; i++) { + if (a.charAt(i) != b.charAt(i)) + return false; + } + return true; + } + + public static int hash(@NotNull CharSequence cs) { + if (cs instanceof String) + return cs.hashCode(); + int h = 0; + for (int i = 0, len = cs.length(); i < len; i++) { + h = 31 * h + cs.charAt(i); + } + return h; + } + + private CharSequences() {} +} diff --git a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMap.java b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMap.java index 81063c02c..1f67e11bb 100644 --- a/src/main/java/net/openhft/chronicle/map/AbstractChronicleMap.java +++ b/src/main/java/net/openhft/chronicle/map/AbstractChronicleMap.java @@ -19,6 +19,9 @@ package net.openhft.chronicle.map; import net.openhft.chronicle.hash.KeyContext; import net.openhft.chronicle.hash.function.SerializableFunction; import net.openhft.chronicle.hash.impl.hashlookup.HashLookupIteration; +import net.openhft.chronicle.hash.impl.util.CharSequences; +import net.openhft.lang.io.Bytes; +import net.openhft.lang.io.MultiStoreBytes; import org.jetbrains.annotations.NotNull; import java.io.File; @@ -33,7 +36,7 @@ import static java.util.Collections.emptyList; import static net.openhft.chronicle.hash.impl.HashContext.SearchState.DELETED; import static net.openhft.chronicle.hash.impl.util.Objects.requireNonNull; -public interface AbstractChronicleMap extends ChronicleMap, Serializable { +interface AbstractChronicleMap extends ChronicleMap, Serializable { @Override VanillaContext context(K key); @@ -370,7 +373,11 @@ public interface AbstractChronicleMap extends ChronicleMap, Serializ return false; try { - return forEachEntryWhile(c -> c.get().equals(m.get(c.key()))); + return forEachEntryWhile(c -> { + K k = c.key(); + V v = (V) m.get(k instanceof CharSequence ? k.toString() : k); + return v != null && c.valueEqualTo(v); + }); } catch (ClassCastException unused) { return false; } catch (NullPointerException unused) { @@ -380,10 +387,20 @@ public interface AbstractChronicleMap extends ChronicleMap, Serializ default int mapHashCode() { int[] h = new int[1]; - forEach((k, v) -> h[0] += k.hashCode() ^ v.hashCode()); + forEach((k, v) -> h[0] += hashCode(k) ^ hashCode(v)); return h[0]; } + // TODO quick and dirty. Think about how generic guava/koloboke equivalence interface could be + // used. See also BytesInterop.equivalent() and hash(). + public static int hashCode(Object obj) { + if (!(obj instanceof CharSequence)) { + return obj.hashCode(); + } else { + return CharSequences.hash((CharSequence) obj); + } + } + default String mapToString() { if (isEmpty()) return "{}"; diff --git a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java index f20e069b4..06176b94d 100644 --- a/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java +++ b/src/test/java/net/openhft/chronicle/map/CHMUseCasesTest.java @@ -1309,7 +1309,6 @@ public class CHMUseCasesTest { valueA.limit(valueA.capacity()); } - //@Ignore("HCOLL-268 JSON serialisation issue") @Test public void testByteBufferDirectByteBufferMap() throws ExecutionException, InterruptedException, IOException { @@ -1340,25 +1339,19 @@ public class CHMUseCasesTest { map.put(key1, value1); mapChecks(); final SerializableFunction function = - new SerializableFunction() { - @Override - public ByteBuffer apply(ByteBuffer s) { - ByteBuffer slice = s.slice(); - slice.limit(2); - return slice; - } + s -> { + ByteBuffer slice = s.slice(); + slice.limit(2); + return slice; }; assertBBEquals(ByteBuffer.wrap(new byte[]{11, 11}), map.getMapped(key1, function)); assertEquals(null, map.getMapped(key2, function)); mapChecks(); assertBBEquals(ByteBuffer.wrap(new byte[]{12, 10}), - map.putMapped(key1, new UnaryOperator() { - @Override - public ByteBuffer update(ByteBuffer s) { - s.put(0, (byte) (s.get(0) + 1)); - s.put(1, (byte) (s.get(1) - 1)); - return function.apply(s); - } + map.putMapped(key1, s -> { + s.put(0, (byte) (s.get(0) + 1)); + s.put(1, (byte) (s.get(1) - 1)); + return function.apply(s); })); assertBBEquals(ByteBuffer.wrap(new byte[]{12, 10}), map.get(key1)); diff --git a/src/test/java/net/openhft/chronicle/map/ChronicleMapEqualsTest.java b/src/test/java/net/openhft/chronicle/map/ChronicleMapEqualsTest.java new file mode 100644 index 000000000..dd3069884 --- /dev/null +++ b/src/test/java/net/openhft/chronicle/map/ChronicleMapEqualsTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014 Higher Frequency Trading http://www.higherfrequencytrading.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.openhft.chronicle.map; + +import org.junit.Test; + +import java.util.HashMap; + +import static org.junit.Assert.assertTrue; + +public class ChronicleMapEqualsTest { + + @Test + public void test() { + ChronicleMap map = + ChronicleMapBuilder + .of(CharSequence.class, CharSequence.class) + .entries(100) + .create(); + + HashMap refMap = new HashMap<>(); + refMap.put("a", "b"); + map.putAll(refMap); + assertTrue(map.equals(refMap)); + } +}