From a4568a781cc6f9bcd8362ab8700c1cfb971b6f53 Mon Sep 17 00:00:00 2001 From: Eduardo Ramirez Date: Fri, 25 Oct 2024 15:09:42 -0700 Subject: [PATCH] chore: make FlatRecordTraversalNode concurrent safe (#699) * chore: make FlatRecordTraversalNode concurrent safe * keep only one path --- .../objectmapper/HollowListTypeMapper.java | 21 +- .../objectmapper/HollowMapTypeMapper.java | 26 +- .../objectmapper/HollowObjectMapper.java | 21 +- .../objectmapper/HollowObjectTypeMapper.java | 332 +---------------- .../objectmapper/HollowSetTypeMapper.java | 24 +- .../write/objectmapper/HollowTypeMapper.java | 6 +- .../flatrecords/FlatRecordOrdinalReader.java | 342 ++++++++++++++++++ .../FlatRecordTraversalListNode.java | 69 ++-- .../traversal/FlatRecordTraversalMapNode.java | 105 ++---- .../traversal/FlatRecordTraversalNode.java | 32 +- .../FlatRecordTraversalObjectNode.java | 186 +++------- ...FlatRecordTraversalObjectNodeEquality.java | 4 +- .../traversal/FlatRecordTraversalSetNode.java | 71 ++-- 13 files changed, 532 insertions(+), 707 deletions(-) create mode 100644 hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordOrdinalReader.java diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowListTypeMapper.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowListTypeMapper.java index c3f80379d..1c84ecc45 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowListTypeMapper.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowListTypeMapper.java @@ -19,13 +19,11 @@ import com.netflix.hollow.api.objects.HollowRecord; import com.netflix.hollow.api.objects.generic.GenericHollowList; import com.netflix.hollow.core.schema.HollowListSchema; -import com.netflix.hollow.core.schema.HollowSchema; import com.netflix.hollow.core.util.IntList; import com.netflix.hollow.core.write.HollowListTypeWriteState; import com.netflix.hollow.core.write.HollowListWriteRecord; import com.netflix.hollow.core.write.HollowTypeWriteState; import com.netflix.hollow.core.write.HollowWriteRecord; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordWriter; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalListNode; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalNode; @@ -34,7 +32,6 @@ import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Set; public class HollowListTypeMapper extends HollowTypeMapper { @@ -129,25 +126,11 @@ protected Object parseHollowRecord(HollowRecord record) { } @Override - protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map parsedObjects) { - List collection = new ArrayList<>(); - - int size = reader.readCollectionSize(); - for (int i = 0; i < size; i++) { - int ordinal = reader.readOrdinal(); - Object element = parsedObjects.get(ordinal); - collection.add(element); - } - - return collection; - } - - @Override - protected Object parseFlatRecordTraversalNode(FlatRecordTraversalNode node) { + protected Object parseFlatRecord(FlatRecordTraversalNode node) { List collection = new ArrayList<>(); for (FlatRecordTraversalNode elementNode : (FlatRecordTraversalListNode) node) { - Object element = elementMapper.parseFlatRecordTraversalNode(elementNode); + Object element = elementMapper.parseFlatRecord(elementNode); collection.add(element); } diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowMapTypeMapper.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowMapTypeMapper.java index 623a37b4d..6de0da6e7 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowMapTypeMapper.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowMapTypeMapper.java @@ -19,14 +19,12 @@ import com.netflix.hollow.api.objects.HollowRecord; import com.netflix.hollow.api.objects.generic.GenericHollowMap; import com.netflix.hollow.core.schema.HollowMapSchema; -import com.netflix.hollow.core.schema.HollowSchema; import com.netflix.hollow.core.util.HollowObjectHashCodeFinder; import com.netflix.hollow.core.write.HollowMapTypeWriteState; import com.netflix.hollow.core.write.HollowMapWriteRecord; import com.netflix.hollow.core.write.HollowTypeWriteState; import com.netflix.hollow.core.write.HollowWriteRecord; import com.netflix.hollow.core.write.HollowWriteStateEngine; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordWriter; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalMapNode; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalNode; @@ -142,31 +140,13 @@ protected Object parseHollowRecord(HollowRecord record) { } @Override - protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map parsedObjects) { - Map collection = new HashMap<>(); - - int size = reader.readCollectionSize(); - int keyOrdinal = 0; - for (int i = 0; i < size; i++) { - int keyOrdinalDelta = reader.readOrdinal(); - int valueOrdinal = reader.readOrdinal(); - keyOrdinal += keyOrdinalDelta; - Object key = parsedObjects.get(keyOrdinal); - Object value = parsedObjects.get(valueOrdinal); - collection.put(key, value); - } - - return collection; - } - - @Override - protected Object parseFlatRecordTraversalNode(FlatRecordTraversalNode node) { + protected Object parseFlatRecord(FlatRecordTraversalNode node) { FlatRecordTraversalMapNode mapNode = (FlatRecordTraversalMapNode) node; Map collection = new HashMap<>(); for (Map.Entry entry : mapNode.entrySet()) { - Object key = keyMapper.parseFlatRecordTraversalNode(entry.getKey()); - Object value = valueMapper.parseFlatRecordTraversalNode(entry.getValue()); + Object key = keyMapper.parseFlatRecord(entry.getKey()); + Object value = valueMapper.parseFlatRecord(entry.getValue()); collection.put(key, value); } return collection; diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapper.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapper.java index a8e8f1ed6..6a1a04b70 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapper.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapper.java @@ -100,28 +100,13 @@ public T readFlat(FlatRecordTraversalNode node) { if (typeMapper == null) { throw new IllegalArgumentException("No type mapper found for schema " + schemaName); } - Object obj = typeMapper.parseFlatRecordTraversalNode(node); + Object obj = typeMapper.parseFlatRecord(node); return (T) obj; } public T readFlat(FlatRecord record) { - FlatRecordReader recordReader = new FlatRecordReader(record); - - int ordinal = 0; - Map parsedObjects = new HashMap<>(); - while(recordReader.hasMore()) { - HollowSchema schema = recordReader.readSchema(); - HollowTypeMapper mapper = typeMappers.get(schema.getName()); - if (mapper == null) { - recordReader.skipSchema(schema); - } else { - Object obj = mapper.parseFlatRecord(schema, recordReader, parsedObjects); - parsedObjects.put(ordinal, obj); - } - ordinal++; - } - - return (T) parsedObjects.get(ordinal - 1); + FlatRecordTraversalNode node = new FlatRecordTraversalObjectNode(record); + return readFlat(node); } /** 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 985663063..ecbac4fdc 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 @@ -22,12 +22,10 @@ import com.netflix.hollow.core.memory.HollowUnsafeHandle; import com.netflix.hollow.core.schema.HollowObjectSchema; import com.netflix.hollow.core.schema.HollowObjectSchema.FieldType; -import com.netflix.hollow.core.schema.HollowSchema; import com.netflix.hollow.core.write.HollowObjectTypeWriteState; import com.netflix.hollow.core.write.HollowObjectWriteRecord; import com.netflix.hollow.core.write.HollowTypeWriteState; import com.netflix.hollow.core.write.HollowWriteRecord; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordWriter; import java.lang.reflect.Field; import java.lang.reflect.Modifier; @@ -37,7 +35,6 @@ import java.util.Date; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalNode; @@ -47,9 +44,9 @@ @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 final 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 final Unsafe unsafe = HollowUnsafeHandle.getUnsafe(); + private final HollowObjectMapper parentMapper; private final String typeName; @@ -220,7 +217,7 @@ protected Object parseHollowRecord(HollowRecord record) { } } } else if (clazz.isEnum()) { - // if `clazz` is an enum, then we should expect to find a field called `_name` in the FlatRecord. + // if `clazz` is an enum, then we should expect to find a field called `_name` in the record. // There may be other fields if the producer enum contained custom properties, we ignore them // here assuming the enum constructor will set them if needed. for (int i = 0; i < objectSchema.numFields(); i++) { @@ -235,7 +232,7 @@ protected Object parseHollowRecord(HollowRecord record) { for (int i = 0; i < objectSchema.numFields(); i++) { int posInPojoSchema = schema.getPosition(objectSchema.getFieldName(i)); if (posInPojoSchema != -1) { - mappedFields.get(posInPojoSchema).copy(hollowObject, obj); + mappedFields.get(posInPojoSchema).copy(obj, hollowObject); } } } @@ -247,74 +244,27 @@ protected Object parseHollowRecord(HollowRecord record) { } @Override - protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map parsedObjects) { - try { - HollowObjectSchema recordObjectSchema = (HollowObjectSchema) recordSchema; - - Object obj = null; - 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++) { - int posInPojoSchema = schema.getPosition(recordObjectSchema.getFieldName(i)); - if (posInPojoSchema != -1) { - obj = mappedFields.get(posInPojoSchema).parseBoxedWrapper(reader); - } else { - reader.skipField(recordObjectSchema.getFieldType(i)); - } - } - } else if (clazz.isEnum()) { - // if `clazz` is an enum, then we should expect to find a field called `_name` in the FlatRecord. - // There may be other fields if the producer enum contained custom properties, we ignore them - // here assuming the enum constructor will set them if needed. - for (int i = 0; i < recordObjectSchema.numFields(); i++) { - String fieldName = recordObjectSchema.getFieldName(i); - int posInPojoSchema = schema.getPosition(fieldName); - if (fieldName.equals(MappedFieldType.ENUM_NAME.getSpecialFieldName()) && posInPojoSchema != -1) { - obj = mappedFields.get(posInPojoSchema).parseBoxedWrapper(reader); - } else { - reader.skipField(recordObjectSchema.getFieldType(i)); - } - } - } else { - obj = unsafe.allocateInstance(clazz); - for (int i = 0; i < recordObjectSchema.numFields(); i++) { - int posInPojoSchema = schema.getPosition(recordObjectSchema.getFieldName(i)); - if (posInPojoSchema != -1) { - mappedFields.get(posInPojoSchema).parse(obj, reader, parsedObjects); - } else { - reader.skipField(recordObjectSchema.getFieldType(i)); - } - } - } - - return obj; - } catch(Exception ex) { - throw new RuntimeException(ex); - } - } - @Override - protected Object parseFlatRecordTraversalNode(FlatRecordTraversalNode node) { + protected Object parseFlatRecord(FlatRecordTraversalNode node) { try { FlatRecordTraversalObjectNode objectNode = (FlatRecordTraversalObjectNode) node; - HollowObjectSchema recordObjectSchema = objectNode.getSchema(); + HollowObjectSchema flatRecordSchema = objectNode.getSchema(); Object obj = null; 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++) { - int posInPojoSchema = schema.getPosition(recordObjectSchema.getFieldName(i)); + for (int i = 0; i < flatRecordSchema.numFields(); i++) { + int posInPojoSchema = schema.getPosition(flatRecordSchema.getFieldName(i)); if (posInPojoSchema != -1) { obj = mappedFields.get(posInPojoSchema).parseBoxedWrapper(objectNode); } } } else if (clazz.isEnum()) { - // if `clazz` is an enum, then we should expect to find a field called `_name` in the FlatRecord. + // if `clazz` is an enum, then we should expect to find a field called `_name` in the record. // There may be other fields if the producer enum contained custom properties, we ignore them // here assuming the enum constructor will set them if needed. - for (int i = 0; i < recordObjectSchema.numFields(); i++) { - String fieldName = recordObjectSchema.getFieldName(i); + for (int i = 0; i < flatRecordSchema.numFields(); i++) { + String fieldName = flatRecordSchema.getFieldName(i); int posInPojoSchema = schema.getPosition(fieldName); if (fieldName.equals(MappedFieldType.ENUM_NAME.getSpecialFieldName()) && posInPojoSchema != -1) { obj = mappedFields.get(posInPojoSchema).parseBoxedWrapper(objectNode); @@ -322,17 +272,16 @@ protected Object parseFlatRecordTraversalNode(FlatRecordTraversalNode node) { } } else { obj = unsafe.allocateInstance(clazz); - for (int i = 0; i < recordObjectSchema.numFields(); i++) { - int posInPojoSchema = schema.getPosition(recordObjectSchema.getFieldName(i)); + for (int i = 0; i < flatRecordSchema.numFields(); i++) { + int posInPojoSchema = schema.getPosition(flatRecordSchema.getFieldName(i)); if (posInPojoSchema != -1) { - mappedFields.get(posInPojoSchema).parse(obj, objectNode); + mappedFields.get(posInPojoSchema).copy(obj, objectNode); } } } return obj; - } - catch(Exception ex) { + } catch (Exception ex) { throw new RuntimeException(ex); } } @@ -623,8 +572,8 @@ public void copy(Object obj, HollowObjectWriteRecord rec, FlatRecordWriter flatR } } - public void copy(GenericHollowObject rec, Object pojo) { - switch(fieldType) { + public void copy(Object pojo, GenericHollowObject rec) { + switch (fieldType) { case BOOLEAN: unsafe.putBoolean(pojo, fieldOffset, rec.getBoolean(fieldName)); break; @@ -817,84 +766,6 @@ private Object parseBoxedWrapper(GenericHollowObject record) { } } - private Object parseBoxedWrapper(FlatRecordReader reader) { - switch (fieldType) { - case BOOLEAN: { - return reader.readBoolean(); - } - case INT: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - return Integer.valueOf(value); - } - break; - } - case SHORT: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - return Short.valueOf((short) value); - } - break; - } - case BYTE: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - return Byte.valueOf((byte) value); - } - break; - } - case CHAR: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - return Character.valueOf((char) value); - } - break; - } - case LONG: { - long value = reader.readLong(); - if (value != Long.MIN_VALUE) { - return Long.valueOf(value); - } - break; - } - case FLOAT: { - float value = reader.readFloat(); - if (!Float.isNaN(value)) { - return Float.valueOf(value); - } - break; - } - case DOUBLE: { - double value = reader.readDouble(); - if (!Double.isNaN(value)) { - return Double.valueOf(value); - } - break; - } - case STRING: { - return reader.readString(); - } - 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; - } - private Object parseBoxedWrapper(FlatRecordTraversalObjectNode record) { switch (fieldType) { case BOOLEAN: @@ -947,8 +818,8 @@ private Object parseBoxedWrapper(FlatRecordTraversalObjectNode record) { } } - private void parse(Object obj, FlatRecordTraversalObjectNode node) { - switch(fieldType) { + private void copy(Object obj, FlatRecordTraversalObjectNode node) { + switch (fieldType) { case BOOLEAN: { Boolean value = node.getFieldValueBooleanBoxed(fieldName); if (value != null) { @@ -1099,7 +970,7 @@ private void parse(Object obj, FlatRecordTraversalObjectNode node) { case REFERENCE: { FlatRecordTraversalNode childNode = node.getFieldNode(fieldName); if (childNode != null) { - unsafe.putObject(obj, fieldOffset, subTypeMapper.parseFlatRecordTraversalNode(childNode)); + unsafe.putObject(obj, fieldOffset, subTypeMapper.parseFlatRecord(childNode)); } break; } @@ -1108,168 +979,6 @@ private void parse(Object obj, FlatRecordTraversalObjectNode node) { } } - - private void parse(Object obj, FlatRecordReader reader, Map parsedRecords) { - switch(fieldType) { - case BOOLEAN: { - Boolean value = reader.readBoolean(); - if (value != null) { - unsafe.putBoolean(obj, fieldOffset, value == Boolean.TRUE); - } - break; - } - case INT: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putInt(obj, fieldOffset, value); - } - break; - } - case SHORT: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putShort(obj, fieldOffset, (short) value); - } - break; - } - case BYTE: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putByte(obj, fieldOffset, (byte) value); - } - break; - } - case CHAR: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putChar(obj, fieldOffset, (char) value); - } - break; - } - case LONG: { - long value = reader.readLong(); - if (value != Long.MIN_VALUE) { - unsafe.putLong(obj, fieldOffset, value); - } - break; - } - case FLOAT: { - float value = reader.readFloat(); - if (!Float.isNaN(value)) { - unsafe.putFloat(obj, fieldOffset, value); - } - break; - } - case DOUBLE: { - double value = reader.readDouble(); - if (!Double.isNaN(value)) { - unsafe.putDouble(obj, fieldOffset, value); - } - break; - } - case STRING: { - String value = reader.readString(); - if (value != null) { - unsafe.putObject(obj, fieldOffset, value); - } - break; - } - case BYTES: { - byte[] value = reader.readBytes(); - if (value != null) { - unsafe.putObject(obj, fieldOffset, value); - } - break; - } - case INLINED_BOOLEAN: { - Boolean value = reader.readBoolean(); - if (value != null) { - unsafe.putObject(obj, fieldOffset, value); - } - break; - } - case INLINED_INT: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putObject(obj, fieldOffset, Integer.valueOf(value)); - } - break; - } - case INLINED_SHORT: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putObject(obj, fieldOffset, Short.valueOf((short) value)); - } - break; - } - case INLINED_BYTE: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putObject(obj, fieldOffset, Byte.valueOf((byte) value)); - } - break; - } - case INLINED_CHAR: { - int value = reader.readInt(); - if (value != Integer.MIN_VALUE) { - unsafe.putObject(obj, fieldOffset, Character.valueOf((char) value)); - } - break; - } - case INLINED_LONG: { - long value = reader.readLong(); - if (value != Long.MIN_VALUE) { - unsafe.putObject(obj, fieldOffset, Long.valueOf(value)); - } - break; - } - case INLINED_FLOAT: { - float value = reader.readFloat(); - if (!Float.isNaN(value)) { - unsafe.putObject(obj, fieldOffset, Float.valueOf(value)); - } - break; - } - case INLINED_DOUBLE: { - double value = reader.readDouble(); - if (!Double.isNaN(value)) { - unsafe.putObject(obj, fieldOffset, Double.valueOf(value)); - } - break; - } - case INLINED_STRING: { - String value = reader.readString(); - if (value != null) { - unsafe.putObject(obj, fieldOffset, value); - } - break; - } - case DATE_TIME: { - long value = reader.readLong(); - if (value != Long.MIN_VALUE) { - unsafe.putObject(obj, fieldOffset, new Date(value)); - } - break; - } - case ENUM_NAME: { - String value = reader.readString(); - if (value != null) { - unsafe.putObject(obj, fieldOffset, Enum.valueOf((Class) type, value)); - } - break; - } - case REFERENCE: { - int ordinal = reader.readOrdinal(); - if (ordinal != -1) { - unsafe.putObject(obj, fieldOffset, parsedRecords.get(ordinal)); - } - break; - } - default: - throw new IllegalArgumentException("Unknown field type: " + fieldType); - } - } - public Object retrieveFieldValue(Object obj, int[] fieldPathIdx, int idx) { Object fieldObject; @@ -1282,7 +991,6 @@ public Object retrieveFieldValue(Object obj, int[] fieldPathIdx, int idx) { return ((HollowObjectTypeMapper)subTypeMapper).retrieveFieldValue(fieldObject, fieldPathIdx, idx+1); } - switch(fieldType) { case BOOLEAN: return unsafe.getBoolean(obj, fieldOffset); diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowSetTypeMapper.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowSetTypeMapper.java index 37bb1fcff..c4896b302 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowSetTypeMapper.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowSetTypeMapper.java @@ -18,7 +18,6 @@ import com.netflix.hollow.api.objects.HollowRecord; import com.netflix.hollow.api.objects.generic.GenericHollowSet; -import com.netflix.hollow.core.schema.HollowSchema; import com.netflix.hollow.core.schema.HollowSetSchema; import com.netflix.hollow.core.util.HollowObjectHashCodeFinder; import com.netflix.hollow.core.write.HollowSetTypeWriteState; @@ -26,7 +25,6 @@ import com.netflix.hollow.core.write.HollowTypeWriteState; import com.netflix.hollow.core.write.HollowWriteRecord; import com.netflix.hollow.core.write.HollowWriteStateEngine; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordWriter; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalNode; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalSetNode; @@ -34,8 +32,6 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashSet; -import java.util.Iterator; -import java.util.Map; import java.util.Set; public class HollowSetTypeMapper extends HollowTypeMapper { @@ -121,26 +117,10 @@ protected Object parseHollowRecord(HollowRecord record) { } @Override - protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map parsedObjects) { - Set collection = new HashSet<>(); - - int size = reader.readCollectionSize(); - int ordinal = 0; - for (int i = 0; i < size; i++) { - int ordinalDelta = reader.readOrdinal(); - ordinal += ordinalDelta; - Object element = parsedObjects.get(ordinal); - collection.add(element); - } - - return collection; - } - - @Override - protected Object parseFlatRecordTraversalNode(FlatRecordTraversalNode node) { + protected Object parseFlatRecord(FlatRecordTraversalNode node) { Set collection = new HashSet<>(); for (FlatRecordTraversalNode elementNode : (FlatRecordTraversalSetNode) node) { - collection.add(elementMapper.parseFlatRecordTraversalNode(elementNode)); + collection.add(elementMapper.parseFlatRecord(elementNode)); } return collection; } diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowTypeMapper.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowTypeMapper.java index 6bf6d1561..3d023c3fb 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowTypeMapper.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowTypeMapper.java @@ -18,11 +18,9 @@ import com.netflix.hollow.api.objects.HollowRecord; import com.netflix.hollow.core.memory.ByteDataArray; -import com.netflix.hollow.core.schema.HollowSchema; import com.netflix.hollow.core.write.HollowTypeWriteState; import com.netflix.hollow.core.write.HollowWriteRecord; import com.netflix.hollow.core.write.HollowWriteStateEngine; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordWriter; import com.netflix.hollow.core.write.objectmapper.flatrecords.traversal.FlatRecordTraversalNode; @@ -47,10 +45,8 @@ public abstract class HollowTypeMapper { protected abstract int writeFlat(Object obj, FlatRecordWriter flatRecordWriter); protected abstract Object parseHollowRecord(HollowRecord record); - - protected abstract Object parseFlatRecord(HollowSchema schema, FlatRecordReader reader, Map parsedObjects); - protected abstract Object parseFlatRecordTraversalNode(FlatRecordTraversalNode node); + protected abstract Object parseFlatRecord(FlatRecordTraversalNode node); protected abstract HollowWriteRecord newWriteRecord(); diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordOrdinalReader.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordOrdinalReader.java new file mode 100644 index 000000000..f512f31e0 --- /dev/null +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/FlatRecordOrdinalReader.java @@ -0,0 +1,342 @@ +package com.netflix.hollow.core.write.objectmapper.flatrecords; + +import com.netflix.hollow.core.memory.encoding.VarInt; +import com.netflix.hollow.core.memory.encoding.ZigZag; +import com.netflix.hollow.core.schema.HollowObjectSchema; +import com.netflix.hollow.core.schema.HollowSchema; +import com.netflix.hollow.core.util.IntList; +import com.netflix.hollow.core.write.HollowObjectWriteRecord; + +public class FlatRecordOrdinalReader { + private final FlatRecord record; + private final IntList ordinalOffsets = new IntList(); + + public FlatRecordOrdinalReader(FlatRecord record) { + this.record = record; + populateOrdinalOffset(); + } + + private void populateOrdinalOffset() { + int offset = record.dataStartByte; + while (offset < record.dataEndByte) { + ordinalOffsets.add(offset); + offset += sizeOfOrdinal(ordinalOffsets.size() - 1); + } + } + + private int getOrdinalOffset(int ordinal) { + return ordinalOffsets.get(ordinal); + } + + public int getOrdinalCount() { + return ordinalOffsets.size(); + } + + public HollowSchema readSchema(int ordinal) { + int schemaId = VarInt.readVInt(record.data, getOrdinalOffset(ordinal)); + return record.schemaIdMapper.getSchema(schemaId); + } + + public int readSize(int ordinal) { + int offset = getOrdinalOffset(ordinal); + + int schemaId = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(schemaId); + + HollowSchema schema = record.schemaIdMapper.getSchema(schemaId); + if (schema.getSchemaType() != HollowSchema.SchemaType.LIST && + schema.getSchemaType() != HollowSchema.SchemaType.SET && + schema.getSchemaType() != HollowSchema.SchemaType.MAP) { + throw new IllegalArgumentException(String.format("Ordinal %d is not a LIST, SET, or MAP type (found %s)", ordinal, schema.getSchemaType())); + } + + return VarInt.readVInt(record.data, offset); + } + + public void readListElementsInto(int ordinal, int[] elements) { + int offset = getOrdinalOffset(ordinal); + + int schemaId = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(schemaId); + + HollowSchema schema = record.schemaIdMapper.getSchema(schemaId); + if (schema.getSchemaType() != HollowSchema.SchemaType.LIST) { + throw new IllegalArgumentException(String.format("Ordinal %d is not a LIST type (found %s)", ordinal, schema.getSchemaType())); + } + + int size = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(size); + + for (int i = 0; i < size; i++) { + elements[i] = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(elements[i]); + } + } + + public void readSetElementsInto(int ordinal, int[] elements) { + int offset = getOrdinalOffset(ordinal); + + int schemaId = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(schemaId); + + HollowSchema schema = record.schemaIdMapper.getSchema(schemaId); + if (schema.getSchemaType() != HollowSchema.SchemaType.SET) { + throw new IllegalArgumentException(String.format("Ordinal %d is not a SET type (found %s)", ordinal, schema.getSchemaType())); + } + + int size = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(size); + + int elementOrdinal = 0; + for (int i = 0; i < size; i++) { + int elementOrdinalDelta = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(elementOrdinalDelta); + elementOrdinal += elementOrdinalDelta; + elements[i] = elementOrdinal; + } + } + + public void readMapElementsInto(int ordinal, int[] keys, int[] values) { + int offset = getOrdinalOffset(ordinal); + + int schemaId = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(schemaId); + + HollowSchema schema = record.schemaIdMapper.getSchema(schemaId); + if (schema.getSchemaType() != HollowSchema.SchemaType.MAP) { + throw new IllegalArgumentException(String.format("Ordinal %d is not a MAP type (found %s)", ordinal, schema.getSchemaType())); + } + + int size = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(size); + + int keyOrdinal = 0; + for (int i = 0; i < size; i++) { + int keyOrdinalDelta = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(keyOrdinalDelta); + keyOrdinal += keyOrdinalDelta; + keys[i] = keyOrdinal; + values[i] = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(values[i]); + } + } + + public int readFieldReference(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.REFERENCE, field); + if (offset == -1) { + return -1; + } + + if (VarInt.readVNull(record.data, offset)) { + return -1; + } + + return VarInt.readVInt(record.data, offset); + } + + public Boolean readFieldBoolean(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.BOOLEAN, field); + if (offset == -1) { + return null; + } + + if (VarInt.readVNull(record.data, offset)) { + return null; + } + + int value = record.data.get(offset); + return value == 1 ? Boolean.TRUE : Boolean.FALSE; + } + + public int readFieldInt(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.INT, field); + if (offset == -1) { + return Integer.MIN_VALUE; + } + + if (VarInt.readVNull(record.data, offset)) { + return Integer.MIN_VALUE; + } + + int value = VarInt.readVInt(record.data, offset); + return ZigZag.decodeInt(value); + } + + public long readFieldLong(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.LONG, field); + if (offset == -1) { + return Long.MIN_VALUE; + } + + if (VarInt.readVNull(record.data, offset)) { + return Long.MIN_VALUE; + } + + long value = VarInt.readVLong(record.data, offset); + return ZigZag.decodeLong(value); + } + + public float readFieldFloat(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.FLOAT, field); + if (offset == -1) { + return Float.NaN; + } + + int value = record.data.readIntBits(offset); + if (value == HollowObjectWriteRecord.NULL_FLOAT_BITS) { + return Float.NaN; + } + + return Float.intBitsToFloat(value); + } + + public double readFieldDouble(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.DOUBLE, field); + if (offset == -1) { + return Double.NaN; + } + + long value = record.data.readLongBits(offset); + if (value == HollowObjectWriteRecord.NULL_DOUBLE_BITS) { + return Double.NaN; + } + + return Double.longBitsToDouble(value); + } + + public String readFieldString(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.STRING, field); + if (offset == -1) { + return null; + } + + if (VarInt.readVNull(record.data, offset)) { + return null; + } + + int length = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(length); + + int cLength = VarInt.countVarIntsInRange(record.data, offset, length); + char[] s = new char[cLength]; + for (int i = 0; i < cLength; i++) { + int charValue = VarInt.readVInt(record.data, offset); + s[i] = (char) charValue; + offset += VarInt.sizeOfVInt(charValue); + } + + return new String(s); + } + + public byte[] readFieldBytes(int ordinal, String field) { + int offset = skipToField(ordinal, HollowObjectSchema.FieldType.BYTES, field); + if (offset == -1) { + return null; + } + + if (VarInt.readVNull(record.data, offset)) { + return null; + } + + int length = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(length); + + byte[] b = new byte[length]; + for (int i = 0; i < length; i++) { + b[i] = record.data.get(offset++); + } + + return b; + } + + private int skipToField(int ordinal, HollowObjectSchema.FieldType fieldType, String field) { + int offset = getOrdinalOffset(ordinal); + + int schemaId = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(schemaId); + + HollowSchema schema = record.schemaIdMapper.getSchema(schemaId); + if (schema.getSchemaType() != HollowSchema.SchemaType.OBJECT) { + throw new IllegalArgumentException(String.format("Ordinal %d is not an OBJECT type (found %s)", ordinal, schema.getSchemaType())); + } + HollowObjectSchema objectSchema = (HollowObjectSchema) schema; + + int fieldIndex = objectSchema.getPosition(field); + if (fieldIndex == -1) { + return -1; + } + + if (fieldType != objectSchema.getFieldType(fieldIndex)) { + throw new IllegalArgumentException(String.format("Field %s is not of type %s", field, fieldType)); + } + + for (int i = 0; i < fieldIndex; i++) { + offset += sizeOfFieldValue(objectSchema.getFieldType(i), offset); + } + + return offset; + } + + private int sizeOfOrdinal(int ordinal) { + int offset = getOrdinalOffset(ordinal); + int start = offset; + + int schemaId = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(schemaId); + + HollowSchema schema = record.schemaIdMapper.getSchema(schemaId); + switch (schema.getSchemaType()) { + case OBJECT: { + HollowObjectSchema objectSchema = (HollowObjectSchema) schema; + for (int i = 0; i < objectSchema.numFields(); i++) { + offset += sizeOfFieldValue(objectSchema.getFieldType(i), offset); + } + break; + } + case LIST: + case SET: { + int size = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(size); + for (int i = 0; i < size; i++) { + offset += VarInt.nextVLongSize(record.data, offset); + } + break; + } + case MAP: { + int size = VarInt.readVInt(record.data, offset); + offset += VarInt.sizeOfVInt(size); + for (int i = 0; i < size; i++) { + offset += VarInt.nextVLongSize(record.data, offset); // key + offset += VarInt.nextVLongSize(record.data, offset); // value + } + break; + } + } + + return offset - start; + } + + private int sizeOfFieldValue(HollowObjectSchema.FieldType fieldType, int offset) { + switch (fieldType) { + case INT: + case LONG: + case REFERENCE: + return VarInt.nextVLongSize(record.data, offset); + case BYTES: + case STRING: + if (VarInt.readVNull(record.data, offset)) { + return 1; + } + int fieldLength = VarInt.readVInt(record.data, offset); + return VarInt.sizeOfVInt(fieldLength) + fieldLength; + case BOOLEAN: + return 1; + case DOUBLE: + return 8; + case FLOAT: + return 4; + default: + throw new IllegalArgumentException("Unsupported field type: " + fieldType); + } + } +} diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalListNode.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalListNode.java index aacb936c9..19ad9f44b 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalListNode.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalListNode.java @@ -2,59 +2,42 @@ import com.netflix.hollow.core.schema.HollowListSchema; import com.netflix.hollow.core.schema.HollowObjectSchema; -import com.netflix.hollow.core.util.IntList; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; +import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordOrdinalReader; import java.util.AbstractList; import java.util.Map; public class FlatRecordTraversalListNode extends AbstractList implements FlatRecordTraversalNode { - private FlatRecordReader reader; - private IntList ordinalPositions; - private HollowListSchema schema; - private int[] elementOrdinals; + private final FlatRecordOrdinalReader reader; + private final HollowListSchema schema; + private final int ordinal; + private final int[] elementOrdinals; + private Map commonSchemaMap; - @Override - public void reposition(FlatRecordReader reader, IntList ordinalPositions, int ordinal) { + public FlatRecordTraversalListNode(FlatRecordOrdinalReader reader, HollowListSchema schema, int ordinal) { this.reader = reader; - this.ordinalPositions = ordinalPositions; - - reader.resetTo(ordinalPositions.get(ordinal)); - schema = (HollowListSchema) reader.readSchema(); + this.ordinal = ordinal; + this.schema = schema; - int size = reader.readCollectionSize(); + int size = reader.readSize(ordinal); elementOrdinals = new int[size]; - for (int i = 0; i < size; i++) { - elementOrdinals[i] = reader.readOrdinal(); - } + reader.readListElementsInto(ordinal, elementOrdinals); } @Override - public void setCommonSchema(Map commonSchema) { - this.commonSchemaMap = commonSchema; + public HollowListSchema getSchema() { + return schema; } @Override - public int hashCode() { - int hashCode = 1; - for (FlatRecordTraversalNode e : this) { - FlatRecordTraversalObjectNode objectNode = (FlatRecordTraversalObjectNode) e; - if (objectNode != null && commonSchemaMap.containsKey(objectNode.getSchema().getName())) { - objectNode.setCommonSchema(commonSchemaMap); - hashCode = 31 * hashCode + objectNode.hashCode(); - } - else if (objectNode == null) { - hashCode = 31 * hashCode; - } - } - return hashCode; + public int getOrdinal() { + return ordinal; } - @Override - public HollowListSchema getSchema() { - return schema; + public void setCommonSchema(Map commonSchema) { + this.commonSchemaMap = commonSchema; } public FlatRecordTraversalObjectNode getObject(int index) { @@ -82,11 +65,27 @@ public FlatRecordTraversalNode get(int index) { if (elementOrdinal == -1) { return null; } - return createAndRepositionNode(reader, ordinalPositions, elementOrdinal); + return createNode(reader, elementOrdinal); } @Override public int size() { return elementOrdinals.length; } + + @Override + public int hashCode() { + int hashCode = 1; + for (FlatRecordTraversalNode e : this) { + FlatRecordTraversalObjectNode objectNode = (FlatRecordTraversalObjectNode) e; + if (objectNode != null && commonSchemaMap.containsKey(objectNode.getSchema().getName())) { + objectNode.setCommonSchema(commonSchemaMap); + hashCode = 31 * hashCode + objectNode.hashCode(); + } + else if (objectNode == null) { + hashCode = 31 * hashCode; + } + } + return hashCode; + } } diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalMapNode.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalMapNode.java index e35b019cb..4e9454771 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalMapNode.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalMapNode.java @@ -2,73 +2,47 @@ import com.netflix.hollow.core.schema.HollowMapSchema; import com.netflix.hollow.core.schema.HollowObjectSchema; -import com.netflix.hollow.core.util.IntList; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; +import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordOrdinalReader; import java.util.AbstractMap; import java.util.AbstractSet; -import java.util.Arrays; -import java.util.Comparator; import java.util.Iterator; import java.util.Map; -import java.util.Objects; import java.util.Set; public class FlatRecordTraversalMapNode extends AbstractMap implements FlatRecordTraversalNode { - private FlatRecordReader reader; - private IntList ordinalPositions; - private HollowMapSchema schema; - private int[] keyOrdinals; - private int[] valueOrdinals; + private final FlatRecordOrdinalReader reader; + private final HollowMapSchema schema; + private final int ordinal; + private final int[] keyOrdinals; + private final int[] valueOrdinals; + private Map commonSchemaMap; - @Override - public void reposition(FlatRecordReader reader, IntList ordinalPositions, int ordinal) { + public FlatRecordTraversalMapNode(FlatRecordOrdinalReader reader, HollowMapSchema schema, int ordinal) { this.reader = reader; - this.ordinalPositions = ordinalPositions; - - reader.resetTo(ordinalPositions.get(ordinal)); - schema = (HollowMapSchema) reader.readSchema(); + this.schema = schema; + this.ordinal = ordinal; - int size = reader.readCollectionSize(); + int size = reader.readSize(ordinal); keyOrdinals = new int[size]; valueOrdinals = new int[size]; - int keyOrdinal = 0; - for (int i = 0; i < size; i++) { - keyOrdinal += reader.readOrdinal(); - keyOrdinals[i] = keyOrdinal; - valueOrdinals[i] = reader.readOrdinal(); - } + reader.readMapElementsInto(ordinal, keyOrdinals, valueOrdinals); } @Override - public void setCommonSchema(Map commonSchema) { - this.commonSchemaMap = commonSchema; + public HollowMapSchema getSchema() { + return schema; } @Override - public int hashCode() { - int h = 0; - Iterator> i = entrySet().iterator(); - while (i.hasNext()) { - Entry e = i.next(); - FlatRecordTraversalNode key = e.getKey(); - FlatRecordTraversalNode value = e.getValue(); - if(commonSchemaMap.containsKey(key.getSchema().getName())) { - key.setCommonSchema(commonSchemaMap); - h += (key == null ? 0 : key.hashCode()); - } - if(commonSchemaMap.containsKey(value.getSchema().getName())) { - value.setCommonSchema(commonSchemaMap); - h += (value == null ? 0 : value.hashCode()); - } - } - return h; + public int getOrdinal() { + return ordinal; } @Override - public HollowMapSchema getSchema() { - return schema; + public void setCommonSchema(Map commonSchema) { + this.commonSchemaMap = commonSchema; } @Override @@ -114,7 +88,7 @@ public K getKey() { if (keyOrdinal == -1) { return null; } - return (K) createAndRepositionNode(reader, ordinalPositions, keyOrdinal); + return (K) createNode(reader, keyOrdinal); } @Override @@ -122,7 +96,7 @@ public V getValue() { if (valueOrdinal == -1) { return null; } - return (V) createAndRepositionNode(reader, ordinalPositions, valueOrdinal); + return (V) createNode(reader, valueOrdinal); } @Override @@ -132,31 +106,22 @@ public V setValue(V value) { }; } } - private static class MapEntry { - private final int key; - private final int value; - - public MapEntry(int key, int value) { - this.key = key; - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (o == this) return true; - if (!(o instanceof MapEntry)) return false; - MapEntry other = (MapEntry) o; - return key == other.key && value == other.value; - } - @Override - public int hashCode() { - return Objects.hash(key, value); - } - - @Override - public String toString() { - return "MapEntry(" + key + ", " + value + ")"; + @Override + public int hashCode() { + int h = 0; + for (Entry e : entrySet()) { + FlatRecordTraversalNode key = e.getKey(); + FlatRecordTraversalNode value = e.getValue(); + if (commonSchemaMap.containsKey(key.getSchema().getName())) { + key.setCommonSchema(commonSchemaMap); + h += key.hashCode(); + } + if (commonSchemaMap.containsKey(value.getSchema().getName())) { + value.setCommonSchema(commonSchemaMap); + h += value.hashCode(); + } } + return h; } } diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalNode.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalNode.java index 9a4dd05b7..550d561a9 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalNode.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalNode.java @@ -1,9 +1,11 @@ package com.netflix.hollow.core.write.objectmapper.flatrecords.traversal; +import com.netflix.hollow.core.schema.HollowListSchema; +import com.netflix.hollow.core.schema.HollowMapSchema; import com.netflix.hollow.core.schema.HollowObjectSchema; import com.netflix.hollow.core.schema.HollowSchema; -import com.netflix.hollow.core.util.IntList; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; +import com.netflix.hollow.core.schema.HollowSetSchema; +import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordOrdinalReader; import java.util.Map; @@ -11,35 +13,25 @@ * An abstraction that allows for the traversal of a flat record from the root type to a specific sub-path. */ public interface FlatRecordTraversalNode { + int getOrdinal(); + HollowSchema getSchema(); void setCommonSchema(Map commonSchema); - void reposition(FlatRecordReader reader, IntList ordinalPositions, int ordinal); - - default FlatRecordTraversalNode createAndRepositionNode(FlatRecordReader reader, IntList ordinalPositions, int ordinal) { - reader.pointer = ordinalPositions.get(ordinal); - HollowSchema schema = reader.readSchema(); - - FlatRecordTraversalNode node; + default FlatRecordTraversalNode createNode(FlatRecordOrdinalReader reader, int ordinal) { + HollowSchema schema = reader.readSchema(ordinal); switch (schema.getSchemaType()) { case OBJECT: - node = new FlatRecordTraversalObjectNode(); - break; + return new FlatRecordTraversalObjectNode(reader, (HollowObjectSchema) schema, ordinal); case LIST: - node = new FlatRecordTraversalListNode(); - break; + return new FlatRecordTraversalListNode(reader, (HollowListSchema) schema, ordinal); case SET: - node = new FlatRecordTraversalSetNode(); - break; + return new FlatRecordTraversalSetNode(reader, (HollowSetSchema) schema, ordinal); case MAP: - node = new FlatRecordTraversalMapNode(); - break; + return new FlatRecordTraversalMapNode(reader, (HollowMapSchema) schema, ordinal); default: throw new IllegalArgumentException("Unsupported schema type: " + schema.getSchemaType()); } - - node.reposition(reader, ordinalPositions, ordinal); - return node; } } \ No newline at end of file diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalObjectNode.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalObjectNode.java index b022c08b8..1a303289b 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalObjectNode.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalObjectNode.java @@ -1,50 +1,40 @@ package com.netflix.hollow.core.write.objectmapper.flatrecords.traversal; import com.netflix.hollow.core.schema.HollowObjectSchema; -import com.netflix.hollow.core.schema.HollowSchema; -import com.netflix.hollow.core.util.IntList; import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecord; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; +import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordOrdinalReader; import java.util.Arrays; import java.util.Map; import java.util.Objects; public class FlatRecordTraversalObjectNode implements FlatRecordTraversalNode { - private FlatRecordReader reader; - private IntList ordinalPositions; - private HollowObjectSchema schema; + private final FlatRecordOrdinalReader reader; + private final HollowObjectSchema schema; + private final int ordinal; + private Map commonSchemaMap; - private int position; - public FlatRecordTraversalObjectNode() {} + public FlatRecordTraversalObjectNode(FlatRecordOrdinalReader reader, HollowObjectSchema schema, int ordinal) { + this.reader = reader; + this.schema = schema; + this.ordinal = ordinal; + } public FlatRecordTraversalObjectNode(FlatRecord rec) { - FlatRecordReader reader = new FlatRecordReader(rec); - - IntList ordinalPositions = new IntList(); - while (reader.hasMore()) { - ordinalPositions.add(reader.pointer); - HollowSchema schema = reader.readSchema(); - reader.skipSchema(schema); - } - - reposition(reader, ordinalPositions, ordinalPositions.size() - 1); + this.reader = new FlatRecordOrdinalReader(rec); + this.ordinal = reader.getOrdinalCount() - 1; + this.schema = (HollowObjectSchema) reader.readSchema(ordinal); } @Override - public void reposition(FlatRecordReader reader, IntList ordinalPositions, int ordinal) { - this.reader = reader; - this.ordinalPositions = ordinalPositions; - - reader.resetTo(ordinalPositions.get(ordinal)); - schema = (HollowObjectSchema) reader.readSchema(); - position = reader.pointer; + public HollowObjectSchema getSchema() { + return schema; } @Override - public HollowObjectSchema getSchema() { - return schema; + public int getOrdinal() { + return ordinal; } @Override @@ -69,53 +59,53 @@ public FlatRecordTraversalMapNode getMapFieldNode(String field) { } public FlatRecordTraversalNode getFieldNode(String field) { - if (!skipToField(field)) { + HollowObjectSchema.FieldType fieldType = schema.getFieldType(field); + if (fieldType == null) { return null; } - if (schema.getFieldType(field) != HollowObjectSchema.FieldType.REFERENCE) { - throw new IllegalStateException("Cannot get child for non-reference field"); + if (fieldType != HollowObjectSchema.FieldType.REFERENCE) { + throw new IllegalArgumentException("Cannot get child for non-reference field: " + field); } - int refOrdinal = reader.readOrdinal(); + int refOrdinal = reader.readFieldReference(ordinal, field); if (refOrdinal == -1) { return null; } - return createAndRepositionNode(reader, ordinalPositions, refOrdinal); + return createNode(reader, refOrdinal); } public Object getFieldValue(String field) { - if (!skipToField(field)) { + HollowObjectSchema.FieldType fieldType = schema.getFieldType(field); + if (fieldType == null) { return null; } - switch(schema.getFieldType(field)) { + + switch (fieldType) { case BOOLEAN: - return reader.readBoolean(); + return reader.readFieldBoolean(ordinal, field); case INT: - return reader.readInt(); + return reader.readFieldInt(ordinal, field); case LONG: - return reader.readLong(); + return reader.readFieldLong(ordinal, field); case FLOAT: - return reader.readFloat(); + return reader.readFieldFloat(ordinal, field); case DOUBLE: - return reader.readDouble(); + return reader.readFieldDouble(ordinal, field); case STRING: - return reader.readString(); + return reader.readFieldString(ordinal, field); case BYTES: - return reader.readBytes(); + return reader.readFieldBytes(ordinal, field); case REFERENCE: - throw new IllegalStateException("Cannot get leaf value for reference field"); + throw new IllegalArgumentException("Cannot get leaf value for reference field: " + field); } return null; } public boolean getFieldValueBoolean(String field) { - if (!skipToField(field)) { - return false; - } - assertFieldType(field, HollowObjectSchema.FieldType.BOOLEAN); - return reader.readBoolean(); + Boolean b = reader.readFieldBoolean(ordinal, field); + return Boolean.TRUE.equals(b); } public Boolean getFieldValueBooleanBoxed(String field) { @@ -123,11 +113,7 @@ public Boolean getFieldValueBooleanBoxed(String field) { } public int getFieldValueInt(String field) { - if (!skipToField(field)) { - return Integer.MIN_VALUE; - } - assertFieldType(field, HollowObjectSchema.FieldType.INT); - return reader.readInt(); + return reader.readFieldInt(ordinal, field); } public Integer getFieldValueIntBoxed(String field) { @@ -139,11 +125,7 @@ public Integer getFieldValueIntBoxed(String field) { } public long getFieldValueLong(String field) { - if (!skipToField(field)) { - return Long.MIN_VALUE; - } - assertFieldType(field, HollowObjectSchema.FieldType.LONG); - return reader.readLong(); + return reader.readFieldLong(ordinal, field); } public Long getFieldValueLongBoxed(String field) { @@ -155,11 +137,7 @@ public Long getFieldValueLongBoxed(String field) { } public float getFieldValueFloat(String field) { - if (!skipToField(field)) { - return Float.NaN; - } - assertFieldType(field, HollowObjectSchema.FieldType.FLOAT); - return reader.readFloat(); + return reader.readFieldFloat(ordinal, field); } public Float getFieldValueFloatBoxed(String field) { @@ -171,11 +149,7 @@ public Float getFieldValueFloatBoxed(String field) { } public double getFieldValueDouble(String field) { - if (!skipToField(field)) { - return Double.NaN; - } - assertFieldType(field, HollowObjectSchema.FieldType.DOUBLE); - return reader.readDouble(); + return reader.readFieldDouble(ordinal, field); } public Double getFieldValueDoubleBoxed(String field) { @@ -187,93 +161,23 @@ public Double getFieldValueDoubleBoxed(String field) { } public String getFieldValueString(String field) { - if (!skipToField(field)) { - return null; - } - assertFieldType(field, HollowObjectSchema.FieldType.STRING); - return reader.readString(); + return reader.readFieldString(ordinal, field); } public byte[] getFieldValueBytes(String field) { - if (!skipToField(field)) { - return null; - } - assertFieldType(field, HollowObjectSchema.FieldType.BYTES); - return reader.readBytes(); + return reader.readFieldBytes(ordinal, field); } @Override public int hashCode() { HollowObjectSchema commonSchema = commonSchemaMap.get(schema.getName()); Object[] fields = new Object[commonSchema.numFields()]; - for(int i=0;i commonSchemaCache = new HashMap<>(); + private static final Map commonSchemaCache = new HashMap<>(); public static boolean equals(FlatRecordTraversalObjectNode left, FlatRecordTraversalObjectNode right) { if (left == null && right == null) { @@ -138,5 +137,4 @@ else if (left instanceof FlatRecordTraversalMapNode && right instanceof FlatReco } } } - } diff --git a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalSetNode.java b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalSetNode.java index 1cd728636..d14cb8496 100644 --- a/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalSetNode.java +++ b/hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/flatrecords/traversal/FlatRecordTraversalSetNode.java @@ -2,64 +2,45 @@ import com.netflix.hollow.core.schema.HollowObjectSchema; import com.netflix.hollow.core.schema.HollowSetSchema; -import com.netflix.hollow.core.util.IntList; -import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordReader; +import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecordOrdinalReader; import java.util.AbstractSet; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; import java.util.Iterator; -import java.util.List; import java.util.Map; -import java.util.Set; public class FlatRecordTraversalSetNode extends AbstractSet implements FlatRecordTraversalNode { - private FlatRecordReader reader; - private IntList ordinalPositions; - private HollowSetSchema schema; - private int[] elementOrdinals; + private final FlatRecordOrdinalReader reader; + private final HollowSetSchema schema; + private final int ordinal; + private final int[] elementOrdinals; private Map commonSchemaMap; - @Override - public void reposition(FlatRecordReader reader, IntList ordinalPositions, int ordinal) { - this.reader = reader; - this.ordinalPositions = ordinalPositions; - reader.resetTo(ordinalPositions.get(ordinal)); - schema = (HollowSetSchema) reader.readSchema(); + public FlatRecordTraversalSetNode(FlatRecordOrdinalReader reader, HollowSetSchema schema, int ordinal) { + this.reader = reader; + this.ordinal = ordinal; + this.schema = schema; - int size = reader.readCollectionSize(); + int size = reader.readSize(ordinal); elementOrdinals = new int[size]; - int elementOrdinal = 0; - for (int i = 0; i < size; i++) { - elementOrdinal += reader.readOrdinal(); - elementOrdinals[i] = elementOrdinal; - } + reader.readSetElementsInto(ordinal, elementOrdinals); } - @Override - public void setCommonSchema(Map commonSchema) { - this.commonSchemaMap = commonSchema; - } @Override public HollowSetSchema getSchema() { return schema; } @Override - public int hashCode() { - int h = 0; - Iterator i = iterator(); - while (i.hasNext()) { - FlatRecordTraversalNode obj = i.next(); - if (obj != null && commonSchemaMap.containsKey(obj.getSchema().getName())) - obj.setCommonSchema(commonSchemaMap); - h += obj.hashCode(); - } - return h; + public int getOrdinal() { + return ordinal; + } + + @Override + public void setCommonSchema(Map commonSchema) { + this.commonSchemaMap = commonSchema; } + public Iterator objects() { return new IteratorImpl<>(); } @@ -86,6 +67,18 @@ public int size() { return elementOrdinals.length; } + @Override + public int hashCode() { + int h = 0; + for (FlatRecordTraversalNode obj : this) { + if (obj != null && commonSchemaMap.containsKey(obj.getSchema().getName())) { + obj.setCommonSchema(commonSchemaMap); + h += obj.hashCode(); + } + } + return h; + } + private class IteratorImpl implements Iterator { private int index = 0; @@ -100,7 +93,7 @@ public T next() { if (elementOrdinal == -1) { return null; } - return (T) createAndRepositionNode(reader, ordinalPositions, elementOrdinal); + return (T) createNode(reader, elementOrdinal); } } }