Skip to content

Commit 8857c90

Browse files
feat: add support for going from HollowGenericObject/HollowRecod/ReadStateEngine to POJO (#662)
* feat: add support for going from HollowGenericObject/HollowRecod/ReadStateEngine to POJO * handle nulls
1 parent f045da2 commit 8857c90

File tree

7 files changed

+1075
-1
lines changed

7 files changed

+1075
-1
lines changed

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowListTypeMapper.java

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package com.netflix.hollow.core.write.objectmapper;
1818

19+
import com.netflix.hollow.api.objects.HollowRecord;
20+
import com.netflix.hollow.api.objects.generic.GenericHollowList;
1921
import com.netflix.hollow.core.schema.HollowListSchema;
2022
import com.netflix.hollow.core.schema.HollowSchema;
2123
import com.netflix.hollow.core.util.IntList;
@@ -113,6 +115,16 @@ private HollowListWriteRecord copyToWriteRecord(List<?> l, FlatRecordWriter flat
113115
return rec;
114116
}
115117

118+
@Override
119+
protected Object parseHollowRecord(HollowRecord record) {
120+
GenericHollowList hollowList = (GenericHollowList) record;
121+
List<Object> list = new ArrayList<>();
122+
for (HollowRecord element : hollowList) {
123+
list.add(elementMapper.parseHollowRecord(element));
124+
}
125+
return list;
126+
}
127+
116128
@Override
117129
protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map<Integer, Object> parsedObjects) {
118130
List<Object> collection = new ArrayList<>();

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowMapTypeMapper.java

+14
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package com.netflix.hollow.core.write.objectmapper;
1818

19+
import com.netflix.hollow.api.objects.HollowRecord;
20+
import com.netflix.hollow.api.objects.generic.GenericHollowMap;
1921
import com.netflix.hollow.core.schema.HollowMapSchema;
2022
import com.netflix.hollow.core.schema.HollowSchema;
2123
import com.netflix.hollow.core.util.HollowObjectHashCodeFinder;
@@ -124,6 +126,18 @@ private HollowMapWriteRecord copyToWriteRecord(Map<?, ?> m, FlatRecordWriter fla
124126
return rec;
125127
}
126128

129+
@Override
130+
protected Object parseHollowRecord(HollowRecord record) {
131+
GenericHollowMap hollowMap = (GenericHollowMap) record;
132+
Map<Object, Object> m = new HashMap<>();
133+
for (Map.Entry<HollowRecord, HollowRecord> entry : hollowMap.entries()) {
134+
Object key = keyMapper.parseHollowRecord(entry.getKey());
135+
Object value = valueMapper.parseHollowRecord(entry.getValue());
136+
m.put(key, value);
137+
}
138+
return m;
139+
}
140+
127141
@Override
128142
protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map<Integer, Object> parsedObjects) {
129143
Map<Object, Object> collection = new HashMap<>();

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectMapper.java

+9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package com.netflix.hollow.core.write.objectmapper;
1818

19+
import com.netflix.hollow.api.objects.HollowRecord;
1920
import com.netflix.hollow.core.schema.HollowSchema;
2021
import com.netflix.hollow.core.write.HollowWriteStateEngine;
2122
import com.netflix.hollow.core.write.objectmapper.flatrecords.FlatRecord;
@@ -76,6 +77,14 @@ public int add(Object o) {
7677
HollowTypeMapper typeMapper = getTypeMapper(o.getClass(), null, null);
7778
return typeMapper.write(o);
7879
}
80+
81+
public <T> T readHollowRecord(HollowRecord record) {
82+
HollowTypeMapper typeMapper = typeMappers.get(record.getSchema().getName());
83+
if (typeMapper == null) {
84+
throw new IllegalArgumentException("No type mapper found for schema " + record.getSchema().getName());
85+
}
86+
return (T) typeMapper.parseHollowRecord(record);
87+
}
7988

8089
public void writeFlat(Object o, FlatRecordWriter flatRecordWriter) {
8190
HollowTypeMapper typeMapper = getTypeMapper(o.getClass(), null, null);

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowObjectTypeMapper.java

+238-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package com.netflix.hollow.core.write.objectmapper;
1818

19+
import com.netflix.hollow.api.objects.HollowRecord;
20+
import com.netflix.hollow.api.objects.generic.GenericHollowObject;
1921
import com.netflix.hollow.core.index.key.PrimaryKey;
2022
import com.netflix.hollow.core.memory.HollowUnsafeHandle;
2123
import com.netflix.hollow.core.schema.HollowObjectSchema;
@@ -194,6 +196,49 @@ private HollowObjectWriteRecord copyToWriteRecord(Object obj, FlatRecordWriter f
194196
return rec;
195197
}
196198

199+
@Override
200+
protected Object parseHollowRecord(HollowRecord record) {
201+
try {
202+
GenericHollowObject hollowObject = (GenericHollowObject) record;
203+
204+
HollowObjectSchema objectSchema = (HollowObjectSchema) record.getSchema();
205+
Object obj = null;
206+
if (BOXED_WRAPPERS.contains(clazz)) {
207+
// if `clazz` is a BoxedWrapper then by definition its OBJECT schema will have a single primitive
208+
// field so find it in the HollowObject and ignore all other fields.
209+
for (int i = 0; i < objectSchema.numFields(); i++) {
210+
int posInPojoSchema = schema.getPosition(objectSchema.getFieldName(i));
211+
if (posInPojoSchema != -1) {
212+
obj = mappedFields.get(posInPojoSchema).parseBoxedWrapper(hollowObject);
213+
}
214+
}
215+
} else if (clazz.isEnum()) {
216+
// if `clazz` is an enum, then we should expect to find a field called `_name` in the FlatRecord.
217+
// There may be other fields if the producer enum contained custom properties, we ignore them
218+
// here assuming the enum constructor will set them if needed.
219+
for (int i = 0; i < objectSchema.numFields(); i++) {
220+
String fieldName = objectSchema.getFieldName(i);
221+
int posInPojoSchema = schema.getPosition(fieldName);
222+
if (fieldName.equals(MappedFieldType.ENUM_NAME.getSpecialFieldName()) && posInPojoSchema != -1) {
223+
obj = mappedFields.get(posInPojoSchema).parseBoxedWrapper(hollowObject);
224+
}
225+
}
226+
} else {
227+
obj = unsafe.allocateInstance(clazz);
228+
for (int i = 0; i < objectSchema.numFields(); i++) {
229+
int posInPojoSchema = schema.getPosition(objectSchema.getFieldName(i));
230+
if (posInPojoSchema != -1) {
231+
mappedFields.get(posInPojoSchema).copy(hollowObject, obj);
232+
}
233+
}
234+
}
235+
236+
return obj;
237+
} catch (Exception e) {
238+
throw new RuntimeException(e);
239+
}
240+
}
241+
197242
@Override
198243
protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map<Integer, Object> parsedObjects) {
199244
try {
@@ -527,7 +572,199 @@ public void copy(Object obj, HollowObjectWriteRecord rec, FlatRecordWriter flatR
527572
break;
528573
}
529574
}
530-
575+
576+
public void copy(GenericHollowObject rec, Object pojo) {
577+
switch(fieldType) {
578+
case BOOLEAN:
579+
unsafe.putBoolean(pojo, fieldOffset, rec.getBoolean(fieldName));
580+
break;
581+
case INT:
582+
int intValue = rec.getInt(fieldName);
583+
if (intValue != Integer.MIN_VALUE) {
584+
unsafe.putInt(pojo, fieldOffset, intValue);
585+
}
586+
break;
587+
case SHORT:
588+
int shortValue = rec.getInt(fieldName);
589+
if (shortValue != Integer.MIN_VALUE) {
590+
unsafe.putShort(pojo, fieldOffset, (short) shortValue);
591+
}
592+
break;
593+
case BYTE:
594+
int byteValue = rec.getInt(fieldName);
595+
if (byteValue != Integer.MIN_VALUE) {
596+
unsafe.putByte(pojo, fieldOffset, (byte) byteValue);
597+
}
598+
break;
599+
case CHAR:
600+
int charValue = rec.getInt(fieldName);
601+
if (charValue != Integer.MIN_VALUE) {
602+
unsafe.putChar(pojo, fieldOffset, (char) charValue);
603+
}
604+
break;
605+
case LONG:
606+
long longValue = rec.getLong(fieldName);
607+
if (longValue != Long.MIN_VALUE) {
608+
unsafe.putLong(pojo, fieldOffset, longValue);
609+
}
610+
break;
611+
case DOUBLE:
612+
double doubleValue = rec.getDouble(fieldName);
613+
if (!Double.isNaN(doubleValue)) {
614+
unsafe.putDouble(pojo, fieldOffset, doubleValue);
615+
}
616+
break;
617+
case FLOAT:
618+
float floatValue = rec.getFloat(fieldName);
619+
if (!Float.isNaN(floatValue)) {
620+
unsafe.putFloat(pojo, fieldOffset, floatValue);
621+
}
622+
break;
623+
case STRING:
624+
unsafe.putObject(pojo, fieldOffset, rec.getString(fieldName));
625+
break;
626+
case BYTES:
627+
unsafe.putObject(pojo, fieldOffset, rec.getBytes(fieldName));
628+
break;
629+
case INLINED_BOOLEAN:
630+
unsafe.putObject(pojo, fieldOffset, Boolean.valueOf(rec.getBoolean(fieldName)));
631+
break;
632+
case INLINED_INT:
633+
int inlinedIntValue = rec.getInt(fieldName);
634+
if (inlinedIntValue != Integer.MIN_VALUE) {
635+
unsafe.putObject(pojo, fieldOffset, Integer.valueOf(inlinedIntValue));
636+
}
637+
break;
638+
case INLINED_SHORT:
639+
int inlinedShortValue = rec.getInt(fieldName);
640+
if (inlinedShortValue != Integer.MIN_VALUE) {
641+
unsafe.putObject(pojo, fieldOffset, Short.valueOf((short) inlinedShortValue));
642+
}
643+
break;
644+
case INLINED_BYTE:
645+
int inlinedByteValue = rec.getInt(fieldName);
646+
if (inlinedByteValue != Integer.MIN_VALUE) {
647+
unsafe.putObject(pojo, fieldOffset, Byte.valueOf((byte) inlinedByteValue));
648+
}
649+
break;
650+
case INLINED_CHAR:
651+
int inlinedCharValue = rec.getInt(fieldName);
652+
if (inlinedCharValue != Integer.MIN_VALUE) {
653+
unsafe.putObject(pojo, fieldOffset, Character.valueOf((char) inlinedCharValue));
654+
}
655+
break;
656+
case INLINED_LONG:
657+
long inlinedLongValue = rec.getLong(fieldName);
658+
if (inlinedLongValue != Long.MIN_VALUE) {
659+
unsafe.putObject(pojo, fieldOffset, Long.valueOf(inlinedLongValue));
660+
}
661+
break;
662+
case INLINED_DOUBLE:
663+
double inlinedDoubleValue = rec.getDouble(fieldName);
664+
if (!Double.isNaN(inlinedDoubleValue)) {
665+
unsafe.putObject(pojo, fieldOffset, Double.valueOf(inlinedDoubleValue));
666+
}
667+
break;
668+
case INLINED_FLOAT:
669+
float inlinedFloatValue = rec.getFloat(fieldName);
670+
if (!Float.isNaN(inlinedFloatValue)) {
671+
unsafe.putObject(pojo, fieldOffset, Float.valueOf(inlinedFloatValue));
672+
}
673+
break;
674+
case INLINED_STRING:
675+
unsafe.putObject(pojo, fieldOffset, rec.getString(fieldName));
676+
break;
677+
case DATE_TIME:
678+
long dateValue = rec.getLong(fieldName);
679+
if (dateValue != Long.MIN_VALUE) {
680+
unsafe.putObject(pojo, fieldOffset, new Date(dateValue));
681+
}
682+
break;
683+
case ENUM_NAME:
684+
String enumNameValue = rec.getString(fieldName);
685+
if (enumNameValue != null) {
686+
unsafe.putObject(pojo, fieldOffset, Enum.valueOf((Class<Enum>) type, enumNameValue));
687+
}
688+
break;
689+
case REFERENCE:
690+
HollowRecord fieldRecord = rec.getReferencedGenericRecord(fieldName);
691+
if(fieldRecord != null) {
692+
unsafe.putObject(pojo, fieldOffset, subTypeMapper.parseHollowRecord(fieldRecord));
693+
}
694+
break;
695+
default:
696+
throw new IllegalArgumentException("Unexpected field type " + fieldType + " for field " + fieldName);
697+
}
698+
}
699+
700+
private Object parseBoxedWrapper(GenericHollowObject record) {
701+
switch (fieldType) {
702+
case BOOLEAN:
703+
return Boolean.valueOf(record.getBoolean(fieldName));
704+
case INT:
705+
int intValue = record.getInt(fieldName);
706+
if (intValue == Integer.MIN_VALUE) {
707+
return null;
708+
}
709+
return Integer.valueOf(intValue);
710+
case SHORT:
711+
int shortValue = record.getInt(fieldName);
712+
if (shortValue == Integer.MIN_VALUE) {
713+
return null;
714+
}
715+
return Short.valueOf((short) shortValue);
716+
case BYTE:
717+
int byteValue = record.getInt(fieldName);
718+
if (byteValue == Integer.MIN_VALUE) {
719+
return null;
720+
}
721+
return Byte.valueOf((byte) byteValue);
722+
case CHAR:
723+
int charValue = record.getInt(fieldName);
724+
if (charValue == Integer.MIN_VALUE) {
725+
return null;
726+
}
727+
return Character.valueOf((char) charValue);
728+
case LONG:
729+
long longValue = record.getLong(fieldName);
730+
if (longValue == Long.MIN_VALUE) {
731+
return null;
732+
}
733+
return Long.valueOf(longValue);
734+
case FLOAT:
735+
float floatValue = record.getFloat(fieldName);
736+
if (Float.isNaN(floatValue)) {
737+
return null;
738+
}
739+
return Float.valueOf(floatValue);
740+
case DOUBLE:
741+
double doubleValue = record.getDouble(fieldName);
742+
if (Double.isNaN(doubleValue)) {
743+
return null;
744+
}
745+
return Double.valueOf(doubleValue);
746+
case STRING:
747+
return record.getString(fieldName);
748+
case BYTES:
749+
return record.getBytes(fieldName);
750+
case ENUM_NAME:
751+
String enumName = record.getString(fieldName);
752+
if (enumName == null) {
753+
return null;
754+
}
755+
return Enum.valueOf((Class<Enum>) clazz, enumName);
756+
case DATE_TIME: {
757+
long dateValue = record.getLong(fieldName);
758+
if (dateValue == Long.MIN_VALUE) {
759+
return null;
760+
}
761+
return new Date(dateValue);
762+
}
763+
default:
764+
throw new IllegalArgumentException("Unexpected field type " + fieldType + " for field " + fieldName);
765+
}
766+
}
767+
531768
private Object parseBoxedWrapper(FlatRecordReader reader) {
532769
switch (fieldType) {
533770
case BOOLEAN: {

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowSetTypeMapper.java

+12
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
*/
1717
package com.netflix.hollow.core.write.objectmapper;
1818

19+
import com.netflix.hollow.api.objects.HollowRecord;
20+
import com.netflix.hollow.api.objects.generic.GenericHollowSet;
1921
import com.netflix.hollow.core.schema.HollowSchema;
2022
import com.netflix.hollow.core.schema.HollowSetSchema;
2123
import com.netflix.hollow.core.util.HollowObjectHashCodeFinder;
@@ -104,6 +106,16 @@ private HollowSetWriteRecord copyToWriteRecord(Set<?> s, FlatRecordWriter flatRe
104106
return rec;
105107
}
106108

109+
@Override
110+
protected Object parseHollowRecord(HollowRecord record) {
111+
GenericHollowSet hollowSet = (GenericHollowSet) record;
112+
Set<Object> s = new HashSet<>();
113+
for (HollowRecord element : hollowSet) {
114+
s.add(elementMapper.parseHollowRecord(element));
115+
}
116+
return s;
117+
}
118+
107119
@Override
108120
protected Object parseFlatRecord(HollowSchema recordSchema, FlatRecordReader reader, Map<Integer, Object> parsedObjects) {
109121
Set<Object> collection = new HashSet<>();

hollow/src/main/java/com/netflix/hollow/core/write/objectmapper/HollowTypeMapper.java

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package com.netflix.hollow.core.write.objectmapper;
1818

19+
import com.netflix.hollow.api.objects.HollowRecord;
1920
import com.netflix.hollow.core.memory.ByteDataArray;
2021
import com.netflix.hollow.core.schema.HollowSchema;
2122
import com.netflix.hollow.core.write.HollowTypeWriteState;
@@ -42,6 +43,8 @@ public abstract class HollowTypeMapper {
4243
protected abstract int write(Object obj);
4344

4445
protected abstract int writeFlat(Object obj, FlatRecordWriter flatRecordWriter);
46+
47+
protected abstract Object parseHollowRecord(HollowRecord record);
4548

4649
protected abstract Object parseFlatRecord(HollowSchema schema, FlatRecordReader reader, Map<Integer, Object> parsedObjects);
4750

0 commit comments

Comments
 (0)