diff --git a/hollow/src/main/java/com/netflix/hollow/core/read/HollowReadFieldUtils.java b/hollow/src/main/java/com/netflix/hollow/core/read/HollowReadFieldUtils.java index 59eae5abd4..7852869406 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/read/HollowReadFieldUtils.java +++ b/hollow/src/main/java/com/netflix/hollow/core/read/HollowReadFieldUtils.java @@ -63,6 +63,26 @@ public static int fieldHashCode(HollowObjectTypeDataAccess typeAccess, int ordin throw new IllegalStateException("I don't know how to hash a " + schema.getFieldType(fieldPosition)); } + public static int hashObject(Object value) { + if(value instanceof Integer) { + return HollowReadFieldUtils.intHashCode((Integer)value); + } else if(value instanceof String) { + return HollowReadFieldUtils.stringHashCode((String)value); + } else if(value instanceof Float) { + return HollowReadFieldUtils.floatHashCode((Float)value); + } else if(value instanceof Double) { + return HollowReadFieldUtils.doubleHashCode((Double)value); + } else if(value instanceof Boolean) { + return HollowReadFieldUtils.booleanHashCode((Boolean) value); + } else if(value instanceof Long) { + return HollowReadFieldUtils.longHashCode((Long) value); + } else if(value instanceof byte[]) { + return HollowReadFieldUtils.byteArrayHashCode((byte[]) value); + } else { + throw new RuntimeException("Unable to hash field of type " + value.getClass().getName()); + } + } + /** * Determine whether two OBJECT field records are exactly equal. * diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectTypeMapper.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectTypeMapper.java index 39ffdc1bee..25a58cdb82 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectTypeMapper.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectTypeMapper.java @@ -42,7 +42,7 @@ @SuppressWarnings("restriction") public class HollowObjectTypeMapper extends HollowTypeMapper { - private static Set> BOXED_WRAPPERS = new HashSet<>(Arrays.asList(Boolean.class, Integer.class, Short.class, Byte.class, Character.class, Long.class, Float.class, Double.class, String.class, byte[].class, Date.class)); + private static Set> BOXED_WRAPPERS = new HashSet<>(Arrays.asList(Boolean.class, Integer.class, Short.class, Byte.class, Character.class, Long.class, Float.class, Double.class, String.class, byte[].class)); private static final Unsafe unsafe = HollowUnsafeHandle.getUnsafe(); private final HollowObjectMapper parentMapper; @@ -195,7 +195,7 @@ protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader rea HollowObjectSchema recordObjectSchema = (HollowObjectSchema) recordSchema; Object obj = null; - if (BOXED_WRAPPERS.contains(clazz) || clazz.isEnum()) { + if (BOXED_WRAPPERS.contains(clazz)) { // if `clazz` is a BoxedWrapper then by definition its OBJECT schema will have a single primitive // field so find it in the FlatRecord and ignore all other fields. for (int i = 0; i < recordObjectSchema.numFields(); i++) { @@ -570,20 +570,6 @@ private Object parseBoxedWrapper(FlatRecordReader reader) { case BYTES: { return reader.readBytes(); } - case ENUM_NAME: { - String enumName = reader.readString(); - if (enumName != null) { - return Enum.valueOf((Class) clazz, enumName); - } - break; - } - case DATE_TIME: { - long value = reader.readLong(); - if (value != Long.MIN_VALUE) { - return new Date(value); - } - break; - } } return null; } diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordExtractor.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordExtractor.java index fc22641ecf..47af7589a3 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordExtractor.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordExtractor.java @@ -51,7 +51,7 @@ public FlatRecordExtractor(HollowReadStateEngine extractFrom, HollowSchemaIdenti this.recordCopiersByType = new HashMap<>(); } - public synchronized FlatRecord extract(String type, int ordinal) { + public FlatRecord extract(String type, int ordinal) { ordinalRemapper.clear(); writer.reset(); diff --git a/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryKeyIndex.java b/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryKeyIndex.java index 5cf315573e..1f163308ab 100644 --- a/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryKeyIndex.java +++ b/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryKeyIndex.java @@ -64,16 +64,7 @@ public void addTypeIndex(PrimaryKey primaryKey) { } public HollowHistoryTypeKeyIndex addTypeIndex(PrimaryKey primaryKey, HollowDataset dataModel) { - HollowHistoryTypeKeyIndex prevKeyIdx = typeKeyIndexes.get(primaryKey.getType()); HollowHistoryTypeKeyIndex keyIdx = new HollowHistoryTypeKeyIndex(primaryKey, dataModel); - // retain any previous indexed fields - if (prevKeyIdx != null) { - for (int i = 0; i < prevKeyIdx.getKeyFields().length; i++) { - if (prevKeyIdx.getKeyFieldIsIndexed()[i]) { - keyIdx.addFieldIndex(prevKeyIdx.getKeyFields()[i], dataModel); - } - } - } typeKeyIndexes.put(primaryKey.getType(), keyIdx); return keyIdx; } diff --git a/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java b/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java index d97d58ce31..314b65ff04 100644 --- a/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java +++ b/hollow/src/main/java/com/netflix/hollow/tools/history/keyindex/HollowHistoryTypeKeyIndex.java @@ -78,10 +78,6 @@ public String[] getKeyFields() { return primaryKey.getFieldPaths(); } - public boolean[] getKeyFieldIsIndexed() { - return keyFieldIsIndexed; - } - public void addFieldIndex(String fieldName, HollowDataset dataModel) { String[] fieldPathParts = PrimaryKey.getCompleteFieldPathParts(dataModel, primaryKey.getType(), fieldName); for (int i = 0; i < primaryKey.numFields(); i++) { @@ -97,20 +93,18 @@ public void initializeKeySchema(HollowObjectTypeReadState initialTypeState) { if (isInitialized) return; HollowObjectSchema schema = initialTypeState.getSchema(); - for (int i= 0; i < keyFieldNames.length; i ++) { - String[] keyFieldPart = keyFieldNames[i]; - fieldTypes[i] = addSchemaField(schema, keyFieldPart, 0); - } + for (String[] keyFieldPart : keyFieldNames) addSchemaField(schema, keyFieldPart, 0); isInitialized = true; } - private FieldType addSchemaField(HollowObjectSchema schema, String[] keyFieldNames, int keyFieldPartPosition) { + private void addSchemaField(HollowObjectSchema schema, String[] keyFieldNames, int keyFieldPartPosition) { int schemaPosition = schema.getPosition(keyFieldNames[keyFieldPartPosition]); if (keyFieldPartPosition < keyFieldNames.length - 1) { HollowObjectSchema nextPartSchema = (HollowObjectSchema) schema.getReferencedTypeState(schemaPosition).getSchema(); - return addSchemaField(nextPartSchema, keyFieldNames, keyFieldPartPosition + 1); + addSchemaField(nextPartSchema, keyFieldNames, keyFieldPartPosition + 1); + } else { + fieldTypes[keyFieldPartPosition] = schema.getFieldType(schemaPosition); } - return schema.getFieldType(schemaPosition); } private void initializeKeyParts(HollowDataset dataModel) { @@ -158,13 +152,13 @@ private void populateAllCurrentRecordKeysIntoIndex(HollowObjectTypeReadState typ } private void writeKeyObject(HollowObjectTypeReadState typeState, int ordinal, boolean isDelta) { - int assignedOrdinal = isDelta ? maxIndexedOrdinal : ordinal; - maxIndexedOrdinal+=1; + int assignedOrdinal = maxIndexedOrdinal; int assignedIndex = ordinalMapping.storeNewRecord(typeState, ordinal, assignedOrdinal); // Identical record already in memory, no need to store fields if(assignedIndex==ORDINAL_NONE) return; + maxIndexedOrdinal+=1; for (int i = 0; i < primaryKey.numFields(); i++) writeKeyField(assignedOrdinal, i); @@ -175,7 +169,7 @@ private void writeKeyField(int assignedOrdinal, int fieldIdx) { return; Object fieldObject = ordinalMapping.getFieldObject(assignedOrdinal, fieldIdx); - int fieldHash = HashCodes.hashInt(hashObject(fieldObject)); + int fieldHash = HashCodes.hashInt(HollowReadFieldUtils.hashObject(fieldObject)); if(!ordinalFieldHashMapping.containsKey(fieldHash)) ordinalFieldHashMapping.put(fieldHash, new IntList()); @@ -183,27 +177,6 @@ private void writeKeyField(int assignedOrdinal, int fieldIdx) { matchingFieldList.add(assignedOrdinal); } - // Retain consistency with rest of Hollow Hashing when hashing boxed primitives - private int hashObject(Object value) { - if(value instanceof Integer) { - return HollowReadFieldUtils.intHashCode((Integer)value); - } else if(value instanceof String) { - return HollowReadFieldUtils.stringHashCode((String)value); - } else if(value instanceof Float) { - return HollowReadFieldUtils.floatHashCode((Float)value); - } else if(value instanceof Double) { - return HollowReadFieldUtils.doubleHashCode((Double)value); - } else if(value instanceof Boolean) { - return HollowReadFieldUtils.booleanHashCode((Boolean) value); - } else if(value instanceof Long) { - return HollowReadFieldUtils.longHashCode((Long) value); - } else if(value instanceof byte[]) { - return HollowReadFieldUtils.byteArrayHashCode((byte[]) value); - } else { - throw new RuntimeException("Unable to hash field of type " + value.getClass().getName()); - } - } - public String getKeyDisplayString(int keyOrdinal) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < primaryKey.numFields(); i++) { @@ -224,40 +197,54 @@ public IntList queryIndexedFields(final String query) { for (int i = 0; i < primaryKey.numFields(); i++) { int hashCode = 0; + Object objectToFind = null; try { switch (fieldTypes[i]) { case INT: final int queryInt = Integer.parseInt(query); hashCode = HollowReadFieldUtils.intHashCode(queryInt); + objectToFind = queryInt; break; case LONG: final long queryLong = Long.parseLong(query); hashCode = HollowReadFieldUtils.longHashCode(queryLong); + objectToFind = queryLong; break; case STRING: hashCode = HashCodes.hashCode(query); + objectToFind = query; break; case DOUBLE: final double queryDouble = Double.parseDouble(query); hashCode = HollowReadFieldUtils.doubleHashCode(queryDouble); + objectToFind = queryDouble; break; case FLOAT: final float queryFloat = Float.parseFloat(query); hashCode = HollowReadFieldUtils.floatHashCode(queryFloat); + objectToFind = queryFloat; break; default: } - addMatches(HashCodes.hashInt(hashCode), matchingKeys); + addMatches(HashCodes.hashInt(hashCode), objectToFind, i, matchingKeys); } catch(NumberFormatException ignore) {} } return matchingKeys; } - public void addMatches(int hashCode, IntList results) { + public void addMatches(int hashCode, Object objectToMatch, int field, IntList results) { if (!ordinalFieldHashMapping.containsKey(hashCode)) return; - IntList res2 = ordinalFieldHashMapping.get(hashCode); - results.addAll(res2); + + IntList matchingOrdinals = ordinalFieldHashMapping.get(hashCode); + for(int i=0;i entry : assignedOrdinalToIndex.entrySet()) { + int assignedOrdinal = entry.getKey(); + int previousIndex = entry.getValue(); + int newIndex = ordinalMappings[previousIndex]; + + assignedOrdinalToIndex.put(assignedOrdinal, newIndex); } this.ordinalMappings = newTable; @@ -154,31 +166,20 @@ private int rehashExistingRecord(Integer[] newTable, int originalHash, int assig while (newTable[newIndex]!=ORDINAL_NONE) newIndex = (newIndex + 1) % newTable.length; - assignedOrdinalToIndex.put(assignedOrdinal, newIndex); newTable[newIndex] = assignedOrdinal; return newIndex; } - public Object getFieldObject(int keyOrdinal, int fieldIndex) { - int index = assignedOrdinalToIndex.get(keyOrdinal); + public Object getFieldObject(int assignedOrdinal, int fieldIndex) { + int index = assignedOrdinalToIndex.get(assignedOrdinal); return indexFieldObjectMapping.get(index)[fieldIndex]; } private int hashKeyRecord(HollowObjectTypeReadState typeState, int ordinal) { int hashCode = 0; - for (int i = 0; i < primaryKey.numFields(); i++) { - - int lastFieldPath = keyFieldIndices[i].length - 1; - int fieldOrdinal = ordinal; - HollowObjectTypeReadState fieldTypeState = typeState; - for (int f = 0; f < lastFieldPath; f++) { - int fieldPosition = keyFieldIndices[i][f]; - fieldOrdinal = fieldTypeState.readOrdinal(fieldOrdinal, fieldPosition); - fieldTypeState = (HollowObjectTypeReadState) fieldTypeState.getSchema().getReferencedTypeState(fieldPosition); - } - - int fieldHashCode = HollowReadFieldUtils.fieldHashCode(fieldTypeState, fieldOrdinal, keyFieldIndices[i][lastFieldPath]); + Object fieldObjectToHash = readValueInState(typeState, ordinal, i); + int fieldHashCode = HollowReadFieldUtils.hashObject(fieldObjectToHash); hashCode = (hashCode * 31) ^ fieldHashCode; } return HashCodes.hashInt(hashCode); diff --git a/hollow/src/test/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapperFlatRecordParserTest.java b/hollow/src/test/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapperFlatRecordParserTest.java index 7575bf43c5..6126be1d89 100644 --- a/hollow/src/test/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapperFlatRecordParserTest.java +++ b/hollow/src/test/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapperFlatRecordParserTest.java @@ -5,7 +5,6 @@ import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecord; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordWriter; import java.util.Arrays; -import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -27,28 +26,11 @@ public void setUp() { mapper.initializeTypeState(InternalTypeA.class); mapper.initializeTypeState(TypeWithCollections.class); mapper.initializeTypeState(VersionedType2.class); - mapper.initializeTypeState(SpecialWrapperTypesTest.class); flatRecordWriter = new FlatRecordWriter( mapper.getStateEngine(), new FakeHollowSchemaIdentifierMapper(mapper.getStateEngine())); } - @Test - public void testSpecialWrapperTypes() { - SpecialWrapperTypesTest wrapperTypesTest = new SpecialWrapperTypesTest(); - wrapperTypesTest.id = 8797182L; - wrapperTypesTest.type = AnEnum.SOME_VALUE_C; - wrapperTypesTest.dateCreated = new Date(); - - flatRecordWriter.reset(); - mapper.writeFlat(wrapperTypesTest, flatRecordWriter); - FlatRecord fr = flatRecordWriter.generateFlatRecord(); - - SpecialWrapperTypesTest result = mapper.readFlat(fr); - - Assert.assertEquals(wrapperTypesTest, result); - } - @Test public void testSimpleTypes() { TypeWithAllSimpleTypes typeWithAllSimpleTypes = new TypeWithAllSimpleTypes(); @@ -551,37 +533,4 @@ public static class SubValue { @HollowInline public String anotherValue; } - - enum AnEnum { - SOME_VALUE_A, - SOME_VALUE_B, - SOME_VALUE_C, - } - - @HollowTypeName(name = "SpecialWrapperTypesTest") - @HollowPrimaryKey(fields = {"id"}) - static class SpecialWrapperTypesTest { - long id; - @HollowTypeName(name = "AnEnum") - AnEnum type; - Date dateCreated; - - @Override - public boolean equals(Object o) { - if(o instanceof SpecialWrapperTypesTest) { - SpecialWrapperTypesTest other = (SpecialWrapperTypesTest)o; - return id == other.id && type == other.type && dateCreated.equals(other.dateCreated); - } - return false; - } - - @Override - public String toString() { - return "SpecialWrapperTypesTest{" + - "id=" + id + - ", type='" + type + '\'' + - ", dateCreated=" + dateCreated + - '}'; - } - } } diff --git a/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryRehashTest.java b/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryRehashTest.java new file mode 100644 index 0000000000..187c5fcda4 --- /dev/null +++ b/hollow/src/test/java/com/netflix/hollow/tools/history/HollowHistoryRehashTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2016-2019 Netflix, Inc. + * + * 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 com.netflix.hollow.tools.history; + +import com.netflix.hollow.core.AbstractStateEngineTest; +import com.netflix.hollow.core.read.engine.HollowReadStateEngine; +import com.netflix.hollow.core.read.engine.object.HollowObjectTypeReadState; +import com.netflix.hollow.core.schema.HollowObjectSchema; +import com.netflix.hollow.core.schema.HollowObjectSchema.FieldType; +import com.netflix.hollow.core.util.StateEngineRoundTripper; +import com.netflix.hollow.core.write.HollowObjectTypeWriteState; +import com.netflix.hollow.core.write.HollowObjectWriteRecord; +import com.netflix.hollow.tools.history.keyindex.HollowHistoryKeyIndex; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; + +public class HollowHistoryRehashTest extends AbstractStateEngineTest { + private HollowObjectSchema schema; + + @Before + public void setUp() { + schema = new HollowObjectSchema("A", 2); + schema.addField("id", FieldType.FLOAT); + schema.addField("anotherField", FieldType.LONG); + + super.setUp(); + } + + @Test + public void correctlyRehashesKeys_beforeAndAfterSnapshot() throws IOException { + HollowReadStateEngine readEngine = StateEngineRoundTripper.roundTripSnapshot(writeStateEngine); + HollowHistory history = new HollowHistory(readEngine, 1L, 1); + HollowHistoryKeyIndex keyIdx = new HollowHistoryKeyIndex(history); + keyIdx.addTypeIndex("A", "id", "anotherField"); + keyIdx.indexTypeField("A", "id"); + keyIdx.indexTypeField("A", "anotherField"); + + // Will rehash before 2069, otherwise couldn't store all values + for(int i=0;i<5000;i++) { + addRecord((float)i, (long)i); + } + + roundTripSnapshot(); + keyIdx.update(readStateEngine, false); + + HollowObjectTypeReadState typeState = (HollowObjectTypeReadState) readStateEngine.getTypeState("A"); + + // Test objects before and after rehash + for(int ordinal=0;ordinal<5000;ordinal++) { + Assert.assertEquals(keyIdx.getRecordKeyOrdinal(typeState, ordinal), ordinal); + String expectedString = (float)ordinal+":"+ordinal; + Assert.assertEquals(keyIdx.getKeyDisplayString("A", ordinal), expectedString); + } + } + + @Test + public void correctlyRehashesKeys_beforeAndAfterDelta() throws IOException { + HollowReadStateEngine readEngine = StateEngineRoundTripper.roundTripSnapshot(writeStateEngine); + HollowHistory history = new HollowHistory(readEngine, 1L, 1); + HollowHistoryKeyIndex keyIdx = new HollowHistoryKeyIndex(history); + keyIdx.addTypeIndex("A", "id", "anotherField"); + keyIdx.indexTypeField("A", "id"); + keyIdx.indexTypeField("A", "anotherField"); + + // Will rehash before 2069, otherwise couldn't store all values + for(int i=0;i<1000;i++) { + addRecord((float)i, (long)i); + } + + roundTripSnapshot(); + keyIdx.update(readStateEngine, false); + + for(int i=1000;i<5000;i++) { + addRecord((float)i, (long)i); + } + + roundTripDelta(); + keyIdx.update(readStateEngine, true); + + HollowObjectTypeReadState typeState = (HollowObjectTypeReadState) readStateEngine.getTypeState("A"); + + // Test objects before and after rehash + for(int ordinal=0;ordinal<5000;ordinal++) { + Assert.assertEquals(keyIdx.getRecordKeyOrdinal(typeState, ordinal), ordinal); + String expectedString = (float)ordinal+":"+ordinal; + Assert.assertEquals(keyIdx.getKeyDisplayString("A", ordinal), expectedString); + } + } + + @Override + protected void initializeTypeStates() { + HollowObjectTypeWriteState writeState = new HollowObjectTypeWriteState(schema); + writeStateEngine.addTypeState(writeState); + } + + private void addRecord(float id, Long secondId) { + HollowObjectWriteRecord aRec = new HollowObjectWriteRecord(schema); + aRec.setFloat("id", id); + aRec.setLong("anotherField", secondId); + writeStateEngine.add("A", aRec); + } + +} diff --git a/hollow/src/test/java/com/netflix/hollow/tools/util/ObjectInternPoolTest.java b/hollow/src/test/java/com/netflix/hollow/tools/util/ObjectInternPoolTest.java new file mode 100644 index 0000000000..ba33963b06 --- /dev/null +++ b/hollow/src/test/java/com/netflix/hollow/tools/util/ObjectInternPoolTest.java @@ -0,0 +1,182 @@ +/* + * Copyright 2016-2019 Netflix, Inc. + * + * 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 com.netflix.hollow.tools.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertSame; + +import org.junit.Before; +import org.junit.Test; + +public class ObjectInternPoolTest { + + GenericInternPool integerPool; + GenericInternPool floatPool; + GenericInternPool doublePool; + GenericInternPool longPool; + ObjectInternPool internPool; + + @Before + public void setup() { + integerPool = new GenericInternPool<>(); + floatPool = new GenericInternPool<>(); + doublePool = new GenericInternPool<>(); + longPool = new GenericInternPool<>(); + + internPool = new ObjectInternPool(); + } + + @Test + public void testInteger() { + // Java caches boxed integers between -127 and 128 + // Must be outside that range to test interning + Integer intObj = 130; + Integer dupIntObj = 130; + + Integer internedInt = integerPool.intern(intObj); + Integer dupInternedInt = integerPool.intern(dupIntObj); + + assertNotSame(intObj, dupIntObj); + assertSame(internedInt, dupInternedInt); + assertEquals(intObj, internedInt); + } + + @Test + public void testFloat() { + Float floatObj = 130f; + Float dupFloatObj = 130f; + + Float internedFloat = floatPool.intern(floatObj); + Float dupInternedFloat = floatPool.intern(dupFloatObj); + + assertNotSame(floatObj, dupFloatObj); + assertSame(internedFloat, dupInternedFloat); + assertEquals(floatObj, internedFloat); + } + + @Test + public void testDouble() { + Double doubleObj = 130d; + Double dupDoubleObj = 130d; + + Double internedDouble = doublePool.intern(doubleObj); + Double dupInternedDouble = doublePool.intern(dupDoubleObj); + + assertNotSame(doubleObj, dupDoubleObj); + assertSame(internedDouble, dupInternedDouble); + assertEquals(doubleObj, internedDouble); + } + + @Test + public void testLong() { + Long longObj = 130L; + Long dupLongObj = 130L; + + Long internedLong = longPool.intern(longObj); + Long dupInternedLong = longPool.intern(dupLongObj); + + assertNotSame(longObj, dupLongObj); + assertSame(internedLong, dupInternedLong); + assertEquals(longObj, internedLong); + } + + @Test + public void testAutoInternInteger() { + //Java should automatically cache these + Integer lowInt = -128; + Integer dupLowInt = -128; + + Integer highInt = 127; + Integer dupHighInt = 127; + + Integer internedLow1 = integerPool.intern(lowInt); + Integer internedLow2 = integerPool.intern(dupLowInt); + + Integer internedHigh1 = integerPool.intern(highInt); + Integer internedHigh2 = integerPool.intern(dupHighInt); + + assertSame(lowInt, dupLowInt); + assertSame(highInt, dupHighInt); + + assertSame(internedLow1, internedLow2); + assertSame(internedHigh1, internedHigh2); + + assertEquals(lowInt, internedLow1); + assertEquals(highInt, internedHigh1); + } + + @Test + public void testAll() { + Integer intObj = 130; + Integer dupIntObj = 130; + + Float floatObj = 140f; + Float dupFloatObj = 140f; + + Double doubleObj = 150d; + Double dupDoubleObj = 150d; + + Long longObj = 160L; + Long dupLongObj = 160L; + + String stringObj = new String("I am groot"); + String dupStringObj = new String("I am groot"); + + Boolean booleanObj = true; + Boolean dupBooleanObj = true; + + assertNotSame(intObj, dupIntObj); + assertNotSame(floatObj, dupFloatObj); + assertNotSame(doubleObj, dupDoubleObj); + assertNotSame(stringObj, dupStringObj); + assertNotSame(longObj, dupLongObj); + //booleans always cached + + Integer internedInt1 = (Integer)internPool.intern(intObj); + Integer internedInt2 = (Integer)internPool.intern(dupIntObj); + + Float internedFloat1 = (Float)internPool.intern(floatObj); + Float internedFloat2 = (Float)internPool.intern(dupFloatObj); + + Double internedDouble1 = (Double)internPool.intern(doubleObj); + Double internedDouble2 = (Double)internPool.intern(dupDoubleObj); + + Long internedLong1 = (Long)internPool.intern(longObj); + Long internedLong2 = (Long)internPool.intern(dupLongObj); + + String internedString1 = (String)internPool.intern(stringObj); + String internedString2 = (String)internPool.intern(dupStringObj); + + Boolean internedBoolean1 = (Boolean)internPool.intern(booleanObj); + Boolean internedBoolean2 = (Boolean)internPool.intern(dupBooleanObj); + + assertSame(internedInt1, internedInt2); + assertSame(internedFloat1, internedFloat2); + assertSame(internedDouble1, internedDouble2); + assertSame(internedLong1, internedLong2); + assertSame(internedString1, internedString2); + assertSame(internedBoolean1, internedBoolean2); + + assertEquals(intObj, internedInt1); + assertEquals(floatObj, internedFloat1); + assertEquals(doubleObj, internedDouble1); + assertEquals(longObj, internedLong1); + assertEquals(stringObj, internedString1); + assertEquals(booleanObj, internedBoolean1); + } +}