package com.rtbhouse.utils.avro; import java.io.File; import java.io.IOException; import java.nio.ByteBuffer; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.ListIterator; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.apache.avro.AvroRuntimeException; import org.apache.avro.Schema; import org.apache.avro.io.Decoder; import org.apache.avro.io.parsing.ResolvingGrammarGenerator; import org.apache.avro.io.parsing.Symbol; import org.apache.commons.lang3.StringUtils; import org.codehaus.jackson.JsonNode; import com.sun.codemodel.JArray; import com.sun.codemodel.JBlock; import com.sun.codemodel.JCatchBlock; import com.sun.codemodel.JClass; import com.sun.codemodel.JClassAlreadyExistsException; import com.sun.codemodel.JConditional; import com.sun.codemodel.JDoLoop; import com.sun.codemodel.JExpr; import com.sun.codemodel.JExpression; import com.sun.codemodel.JFieldVar; import com.sun.codemodel.JForLoop; import com.sun.codemodel.JInvocation; import com.sun.codemodel.JMethod; import com.sun.codemodel.JMod; import com.sun.codemodel.JPackage; import com.sun.codemodel.JStatement; import com.sun.codemodel.JTryBlock; import com.sun.codemodel.JVar; public class FastDeserializerGenerator<T> extends FastDeserializerGeneratorBase<T> { private static final String DECODER = "decoder"; private boolean useGenericTypes; private JMethod schemaMapMethod; private JFieldVar schemaMapField; private Map<Integer, Schema> schemaMap = new HashMap<>(); private Map<Integer, JVar> schemaVarMap = new HashMap<>(); private Map<String, JMethod> deserializeMethodMap = new HashMap<>(); private Map<String, JMethod> skipMethodMap = new HashMap<>(); private Map<JMethod, Set<Class<? extends Exception>>> exceptionFromMethodMap = new HashMap<>(); private SchemaAssistant schemaAssistant; private JClass string; FastDeserializerGenerator(boolean useGenericTypes, Schema writer, Schema reader, File destination, ClassLoader classLoader, String compileClassPath) { super(writer, reader, destination, classLoader, compileClassPath); this.useGenericTypes = useGenericTypes; this.schemaAssistant = new SchemaAssistant(codeModel, useGenericTypes, false); this.string = codeModel.ref(String.class); } public FastDeserializer<T> generateDeserializer() { String className = getClassName(writer, reader, useGenericTypes ? "Generic" : "Specific"); JPackage classPackage = codeModel._package(GENERATED_PACKAGE_NAME); try { deserializerClass = classPackage._class(className); JVar readerSchemaVar = deserializerClass.field(JMod.PRIVATE | JMod.FINAL, Schema.class, "readerSchema"); JMethod constructor = deserializerClass.constructor(JMod.PUBLIC); JVar constructorParam = constructor.param(Schema.class, "readerSchema"); constructor.body().assign(JExpr.refthis(readerSchemaVar.name()), constructorParam); Schema aliasedWriterSchema = Schema.applyAliases(writer, reader); Symbol resolvingGrammar = new ResolvingGrammarGenerator().generate(aliasedWriterSchema, reader); FieldAction fieldAction = FieldAction.fromValues(aliasedWriterSchema.getType(), true, resolvingGrammar); if (useGenericTypes) { schemaMapField = deserializerClass.field(JMod.PRIVATE, codeModel.ref(Map.class).narrow(Integer.class).narrow(Schema.class), "readerSchemaMap"); schemaMapMethod = deserializerClass.method(JMod.PRIVATE | JMod.FINAL, void.class, "schemaMap"); constructor.body().invoke(schemaMapMethod); schemaMapMethod.body().assign(schemaMapField, JExpr._new(codeModel.ref(HashMap.class).narrow(Integer.class).narrow(Schema.class))); registerSchema(aliasedWriterSchema, readerSchemaVar); } JClass readerSchemaClass = schemaAssistant.classFromSchema(reader); deserializerClass._implements(codeModel.ref(FastDeserializer.class).narrow(readerSchemaClass)); JMethod deserializeMethod = deserializerClass.method(JMod.PUBLIC, readerSchemaClass, "deserialize"); JBlock topLevelDeserializeBlock = new JBlock(); switch (aliasedWriterSchema.getType()) { case RECORD: processRecord(readerSchemaVar, aliasedWriterSchema.getName(), aliasedWriterSchema, reader, topLevelDeserializeBlock, fieldAction, JBlock::_return); break; case ARRAY: processArray(readerSchemaVar, "array", aliasedWriterSchema, reader, topLevelDeserializeBlock, fieldAction, JBlock::_return); break; case MAP: processMap(readerSchemaVar, "map", aliasedWriterSchema, reader, topLevelDeserializeBlock, fieldAction, JBlock::_return); break; default: throw new FastDeserializerGeneratorException( "Incorrect top-level writer schema: " + aliasedWriterSchema.getType()); } if (schemaAssistant.getExceptionsFromStringable().isEmpty()) { assignBlockToBody(deserializeMethod, topLevelDeserializeBlock); } else { JTryBlock tryBlock = deserializeMethod.body()._try(); assignBlockToBody(tryBlock, topLevelDeserializeBlock); for (Class<? extends Exception> classException : schemaAssistant.getExceptionsFromStringable()) { JCatchBlock catchBlock = tryBlock._catch(codeModel.ref(classException)); JVar exceptionVar = catchBlock.param("e"); catchBlock.body()._throw(JExpr._new(codeModel.ref(AvroRuntimeException.class)).arg(exceptionVar)); } } deserializeMethod._throws(codeModel.ref(IOException.class)); deserializeMethod.param(Decoder.class, DECODER); Class<FastDeserializer<T>> clazz = compileClass(className); return clazz.getConstructor(Schema.class).newInstance(reader); } catch (JClassAlreadyExistsException e) { throw new FastDeserializerGeneratorException("Class: " + className + " already exists"); } catch (Exception e) { throw new FastDeserializerGeneratorException(e); } } private void processComplexType(JVar fieldSchemaVar, String name, Schema schema, Schema readerFieldSchema, JBlock methodBody, FieldAction action, BiConsumer<JBlock, JExpression> putExpressionIntoParent) { switch (schema.getType()) { case RECORD: processRecord(fieldSchemaVar, schema.getName(), schema, readerFieldSchema, methodBody, action, putExpressionIntoParent); break; case ARRAY: processArray(fieldSchemaVar, name, schema, readerFieldSchema, methodBody, action, putExpressionIntoParent); break; case MAP: processMap(fieldSchemaVar, name, schema, readerFieldSchema, methodBody, action, putExpressionIntoParent); break; case UNION: processUnion(fieldSchemaVar, name, schema, readerFieldSchema, methodBody, action, putExpressionIntoParent); break; default: throw new FastDeserializerGeneratorException("Incorrect complex type: " + action.getType()); } } private void processSimpleType(Schema schema, JBlock methodBody, FieldAction action, BiConsumer<JBlock, JExpression> putExpressionIntoParent) { switch (schema.getType()) { case ENUM: processEnum(schema, methodBody, action, putExpressionIntoParent); break; case FIXED: processFixed(schema, methodBody, action, putExpressionIntoParent); break; default: processPrimitive(schema, methodBody, action, putExpressionIntoParent); } } private void processRecord(JVar recordSchemaVar, String recordName, final Schema recordWriterSchema, final Schema recordReaderSchema, JBlock parentBody, FieldAction recordAction, BiConsumer<JBlock, JExpression> putRecordIntoParent) { ListIterator<Symbol> actionIterator = actionIterator(recordAction); if (methodAlreadyDefined(recordWriterSchema, recordAction.getShouldRead())) { JMethod method = getMethod(recordWriterSchema, recordAction.getShouldRead()); updateActualExceptions(method); JExpression readingExpression = JExpr.invoke(method).arg(JExpr.direct(DECODER)); if (recordAction.getShouldRead()) { putRecordIntoParent.accept(parentBody, readingExpression); } else { parentBody.add((JStatement) readingExpression); } // seek through actionIterator for (Schema.Field field : recordWriterSchema.getFields()) { FieldAction action = seekFieldAction(recordAction.getShouldRead(), field, actionIterator); if (action.getSymbol() == END_SYMBOL) { break; } } if (!recordAction.getShouldRead()) { return; } // seek through actionIterator also for default values Set<String> fieldNamesSet = recordWriterSchema.getFields() .stream().map(Schema.Field::name).collect(Collectors.toSet()); for (Schema.Field readerField : recordReaderSchema.getFields()) { if (!fieldNamesSet.contains(readerField.name())) { forwardToExpectedDefault(actionIterator); seekFieldAction(true, readerField, actionIterator); } } return; } JMethod method = createMethod(recordWriterSchema, recordAction.getShouldRead()); Set<Class<? extends Exception>> exceptionsOnHigherLevel = schemaAssistant.getExceptionsFromStringable(); schemaAssistant.resetExceptionsFromStringable(); if (recordAction.getShouldRead()) { putRecordIntoParent.accept(parentBody, JExpr.invoke(method).arg(JExpr.direct(DECODER))); } else { parentBody.invoke(method).arg(JExpr.direct(DECODER)); } final JBlock methodBody = method.body(); final JVar result; if (recordAction.getShouldRead()) { JClass recordClass = schemaAssistant.classFromSchema(recordWriterSchema); JInvocation newRecord = JExpr._new(schemaAssistant.classFromSchema(recordWriterSchema, false)); if (useGenericTypes) { newRecord = newRecord.arg(schemaMapField.invoke("get").arg(JExpr.lit(getSchemaId(recordWriterSchema)))); } result = methodBody.decl(recordClass, recordName, newRecord); } else { result = null; } for (Schema.Field field : recordWriterSchema.getFields()) { FieldAction action = seekFieldAction(recordAction.getShouldRead(), field, actionIterator); if (action.getSymbol() == END_SYMBOL) { break; } Schema readerFieldSchema = null; JVar fieldSchemaVar = null; BiConsumer<JBlock, JExpression> putExpressionInRecord = null; if (action.getShouldRead()) { Schema.Field readerField = recordReaderSchema.getField(field.name()); readerFieldSchema = readerField.schema(); final int readerFieldPos = readerField.pos(); putExpressionInRecord = (block, expression) -> block.invoke(result, "put") .arg(JExpr.lit(readerFieldPos)).arg(expression); if (useGenericTypes) { fieldSchemaVar = declareSchemaVar(field.schema(), field.name(), recordSchemaVar.invoke("getField").arg(field.name()).invoke("schema")); } } if (SchemaAssistant.isComplexType(field.schema())) { processComplexType(fieldSchemaVar, field.name(), field.schema(), readerFieldSchema, methodBody, action, putExpressionInRecord); } else { // to preserve reader string specific options use reader field schema if (action.getShouldRead() && Schema.Type.STRING.equals(field.schema().getType())) { processSimpleType(readerFieldSchema, methodBody, action, putExpressionInRecord); } else { processSimpleType(field.schema(), methodBody, action, putExpressionInRecord); } } } // Handle default values if (recordAction.getShouldRead()) { Set<String> fieldNamesSet = recordWriterSchema.getFields().stream().map(Schema.Field::name) .collect(Collectors.toSet()); for (Schema.Field readerField : recordReaderSchema.getFields()) { if (!fieldNamesSet.contains(readerField.name())) { forwardToExpectedDefault(actionIterator); seekFieldAction(true, readerField, actionIterator); JVar schemaVar = null; if (useGenericTypes) { schemaVar = declareSchemaVariableForRecordField(readerField.name(), readerField.schema(), recordSchemaVar); } JExpression value = parseDefaultValue(readerField.schema(), readerField.defaultValue(), methodBody, schemaVar, readerField.name()); methodBody.invoke(result, "put").arg(JExpr.lit(readerField.pos())).arg(value); } } } if (recordAction.getShouldRead()) { methodBody._return(result); } exceptionFromMethodMap.put(method, schemaAssistant.getExceptionsFromStringable()); schemaAssistant.setExceptionsFromStringable(exceptionsOnHigherLevel); updateActualExceptions(method); } private void updateActualExceptions(JMethod method) { Set<Class<? extends Exception>> exceptionFromMethod = exceptionFromMethodMap.get(method); for (Class<? extends Exception> exceptionClass : exceptionFromMethod) { method._throws(exceptionClass); schemaAssistant.getExceptionsFromStringable().add(exceptionClass); } } private JExpression parseDefaultValue(Schema schema, JsonNode defaultValue, JBlock body, JVar schemaVar, String fieldName) { Schema.Type schemaType = schema.getType(); // The default value of union is of the first defined type if (Schema.Type.UNION.equals(schemaType)) { schema = schema.getTypes().get(0); schemaType = schema.getType(); if (useGenericTypes) { JInvocation optionSchemaExpression = schemaVar.invoke("getTypes").invoke("get").arg(JExpr.lit(0)); schemaVar = declareSchemaVar(schema, fieldName, optionSchemaExpression); } } // And default value of null is always null if (Schema.Type.NULL.equals(schemaType)) { return JExpr._null(); } if (SchemaAssistant.isComplexType(schema)) { JClass defaultValueClass = schemaAssistant.classFromSchema(schema, false); JInvocation valueInitializationExpr = JExpr._new(defaultValueClass); JVar valueVar; switch (schemaType) { case RECORD: if (useGenericTypes) { valueInitializationExpr = valueInitializationExpr.arg(getSchemaExpr(schema)); } valueVar = body.decl(defaultValueClass, getVariableName("default" + schema.getName()), valueInitializationExpr); for (Iterator<Map.Entry<String, JsonNode>> it = defaultValue.getFields(); it.hasNext();) { Map.Entry<String, JsonNode> subFieldEntry = it.next(); Schema.Field subField = schema.getField(subFieldEntry.getKey()); JVar fieldSchemaVar = null; if (useGenericTypes) { fieldSchemaVar = declareSchemaVariableForRecordField(subField.name(), subField.schema(), schemaVar); } JExpression fieldValue = parseDefaultValue(subField.schema(), subFieldEntry.getValue(), body, fieldSchemaVar, subField.name()); body.invoke(valueVar, "put").arg(JExpr.lit(subField.pos())).arg(fieldValue); } break; case ARRAY: JVar elementSchemaVar = null; if (useGenericTypes) { valueInitializationExpr = valueInitializationExpr .arg(JExpr.lit(defaultValue.size())).arg(getSchemaExpr(schema)); elementSchemaVar = declareSchemaVar(schema.getElementType(), "defaultElementSchema", schemaVar.invoke("getElementType")); } valueVar = body.decl(defaultValueClass, getVariableName("defaultArray"), valueInitializationExpr); for (JsonNode arrayElementValue : defaultValue) { JExpression arrayElementExpression = parseDefaultValue(schema.getElementType(), arrayElementValue, body, elementSchemaVar, "arrayValue"); body.invoke(valueVar, "add").arg(arrayElementExpression); } break; case MAP: JVar mapValueSchemaVar = null; if (useGenericTypes) { mapValueSchemaVar = declareSchemaVar(schema.getValueType(), "defaultMapValueSchema", schemaVar.invoke("getValueType")); } valueVar = body.decl(defaultValueClass, getVariableName("defaultMap"), valueInitializationExpr); for (Iterator<Map.Entry<String, JsonNode>> it = defaultValue.getFields(); it.hasNext();) { Map.Entry<String, JsonNode> mapEntry = it.next(); JExpression mapKeyExpr; if (SchemaAssistant.hasStringableKey(schema)) { mapKeyExpr = JExpr._new(schemaAssistant.keyClassFromMapSchema(schema)).arg(mapEntry.getKey()); } else { mapKeyExpr = JExpr.lit(mapEntry.getKey()); } JExpression mapEntryValueExpression = parseDefaultValue(schema.getValueType(), mapEntry.getValue(), body, mapValueSchemaVar, "mapElement"); body.invoke(valueVar, "put").arg(mapKeyExpr).arg(mapEntryValueExpression); } break; default: throw new FastDeserializerGeneratorException("Incorrect schema type in default value!"); } return valueVar; } else { switch (schemaType) { case ENUM: return schemaAssistant.getEnumValueByName(schema, JExpr.lit(defaultValue.getTextValue()), getSchemaExpr(schema)); case FIXED: JArray fixedBytesArray = JExpr.newArray(codeModel.BYTE); for (char b : defaultValue.getTextValue().toCharArray()) { fixedBytesArray.add(JExpr.lit((byte) b)); } return schemaAssistant.getFixedValue(schema, fixedBytesArray, getSchemaExpr(schema)); case BYTES: JArray bytesArray = JExpr.newArray(codeModel.BYTE); for (byte b : defaultValue.getTextValue().getBytes()) { bytesArray.add(JExpr.lit(b)); } return codeModel.ref(ByteBuffer.class).staticInvoke("wrap").arg(bytesArray); case STRING: return schemaAssistant.getStringableValue(schema, JExpr.lit(defaultValue.getTextValue())); case INT: return JExpr.lit(defaultValue.getIntValue()); case LONG: return JExpr.lit(defaultValue.getLongValue()); case FLOAT: return JExpr.lit((float) defaultValue.getDoubleValue()); case DOUBLE: return JExpr.lit(defaultValue.getDoubleValue()); case BOOLEAN: return JExpr.lit(defaultValue.getBooleanValue()); case NULL: default: throw new FastDeserializerGeneratorException("Incorrect schema type in default value!"); } } } private void processUnion(JVar unionSchemaVar, final String name, final Schema unionSchema, final Schema readerUnionSchema, JBlock body, FieldAction action, BiConsumer<JBlock, JExpression> putValueIntoParent) { JVar unionIndex = body.decl(codeModel.INT, getVariableName("unionIndex"), JExpr.direct(DECODER + ".readIndex()")); JConditional ifBlock = null; for (int i = 0; i < unionSchema.getTypes().size(); i++) { Schema optionSchema = unionSchema.getTypes().get(i); Schema readerOptionSchema = null; FieldAction unionAction; if (Schema.Type.NULL.equals(optionSchema.getType())) { JBlock nullReadBlock = body._if(unionIndex.eq(JExpr.lit(i)))._then().block(); nullReadBlock.directStatement(DECODER + ".readNull();"); if (action.getShouldRead()) { putValueIntoParent.accept(nullReadBlock, JExpr._null()); } continue; } if (action.getShouldRead()) { readerOptionSchema = readerUnionSchema.getTypes().get(i); Symbol.Alternative alternative = null; if (action.getSymbol() instanceof Symbol.Alternative) { alternative = (Symbol.Alternative) action.getSymbol(); } else if (action.getSymbol().production != null) { for (Symbol symbol : action.getSymbol().production) { if (symbol instanceof Symbol.Alternative) { alternative = (Symbol.Alternative) symbol; break; } } } if (alternative == null) { throw new FastDeserializerGeneratorException("Unable to determine action for field: " + name); } Symbol.UnionAdjustAction unionAdjustAction = (Symbol.UnionAdjustAction) alternative.symbols[i].production[0]; unionAction = FieldAction.fromValues(optionSchema.getType(), action.getShouldRead(), unionAdjustAction.symToParse); } else { unionAction = FieldAction.fromValues(optionSchema.getType(), false, EMPTY_SYMBOL); } JExpression condition = unionIndex.eq(JExpr.lit(i)); ifBlock = ifBlock != null ? ifBlock._elseif(condition) : body._if(condition); final JBlock thenBlock = ifBlock._then(); JVar optionSchemaVar = null; if (useGenericTypes && unionAction.getShouldRead()) { JInvocation optionSchemaExpression = unionSchemaVar.invoke("getTypes").invoke("get").arg(JExpr.lit(i)); optionSchemaVar = declareSchemaVar(optionSchema, name + "OptionSchema", optionSchemaExpression); } if (SchemaAssistant.isComplexType(optionSchema)) { String optionName = name + "Option"; if (Schema.Type.UNION.equals(optionSchema.getType())) { throw new FastDeserializerGeneratorException("Union cannot be sub-type of union!"); } processComplexType(optionSchemaVar, optionName, optionSchema, readerOptionSchema, thenBlock, unionAction, putValueIntoParent); } else { // to preserve reader string specific options use reader option schema if (action.getShouldRead() && Schema.Type.STRING.equals(optionSchema.getType())) { processSimpleType(readerOptionSchema, thenBlock, unionAction, putValueIntoParent); } else { processSimpleType(optionSchema, thenBlock, unionAction, putValueIntoParent); } } } } private void processArray(JVar arraySchemaVar, final String name, final Schema arraySchema, final Schema readerArraySchema, JBlock parentBody, FieldAction action, BiConsumer<JBlock, JExpression> putArrayIntoParent) { if (action.getShouldRead()) { Symbol valuesActionSymbol = null; for (Symbol symbol : action.getSymbol().production) { if (Symbol.Kind.REPEATER.equals(symbol.kind) && "array-end".equals(getSymbolPrintName(((Symbol.Repeater) symbol).end))) { valuesActionSymbol = symbol; break; } } if (valuesActionSymbol == null) { throw new FastDeserializerGeneratorException("Unable to determine action for array: " + name); } action = FieldAction.fromValues(arraySchema.getElementType().getType(), action.getShouldRead(), valuesActionSymbol); } else { action = FieldAction.fromValues(arraySchema.getElementType().getType(), false, EMPTY_SYMBOL); } final JVar arrayVar = action.getShouldRead() ? declareValueVar(name, readerArraySchema, parentBody) : null; JVar chunkLen = parentBody.decl(codeModel.LONG, getVariableName("chunkLen"), JExpr.direct(DECODER + ".readArrayStart()")); JConditional conditional = parentBody._if(chunkLen.gt(JExpr.lit(0))); JBlock ifBlock = conditional._then(); JClass arrayClass = schemaAssistant.classFromSchema(action.getShouldRead() ? readerArraySchema : arraySchema, false); if (action.getShouldRead()) { JInvocation newArrayExp = JExpr._new(arrayClass); if (useGenericTypes) { newArrayExp = newArrayExp.arg(JExpr.cast(codeModel.INT, chunkLen)).arg(getSchemaExpr(arraySchema)); } ifBlock.assign(arrayVar, newArrayExp); JBlock elseBlock = conditional._else(); if (useGenericTypes) { elseBlock.assign(arrayVar, JExpr._new(arrayClass).arg(JExpr.lit(0)).arg(getSchemaExpr(arraySchema))); } else { elseBlock.assign(arrayVar, codeModel.ref(Collections.class).staticInvoke("emptyList")); } } JDoLoop doLoop = ifBlock._do(chunkLen.gt(JExpr.lit(0))); JForLoop forLoop = doLoop.body()._for(); JVar counter = forLoop.init(codeModel.INT, getVariableName("counter"), JExpr.lit(0)); forLoop.test(counter.lt(chunkLen)); forLoop.update(counter.incr()); JBlock forBody = forLoop.body(); JVar elementSchemaVar = null; BiConsumer<JBlock, JExpression> putValueInArray = null; if (action.getShouldRead()) { putValueInArray = (block, expression) -> block.invoke(arrayVar, "add").arg(expression); if (useGenericTypes) { elementSchemaVar = declareSchemaVar(arraySchema.getElementType(), name + "ArrayElemSchema", arraySchemaVar.invoke("getElementType")); } } if (SchemaAssistant.isComplexType(arraySchema.getElementType())) { String elemName = name + "Elem"; Schema readerArrayElementSchema = action.getShouldRead() ? readerArraySchema.getElementType() : null; processComplexType(elementSchemaVar, elemName, arraySchema.getElementType(), readerArrayElementSchema, forBody, action, putValueInArray); } else { // to preserve reader string specific options use reader array schema if (action.getShouldRead() && Schema.Type.STRING.equals(arraySchema.getElementType().getType())) { processSimpleType(readerArraySchema.getElementType(), forBody, action, putValueInArray); } else { processSimpleType(arraySchema.getElementType(), forBody, action, putValueInArray); } } doLoop.body().assign(chunkLen, JExpr.direct(DECODER + ".arrayNext()")); if (action.getShouldRead()) { putArrayIntoParent.accept(parentBody, arrayVar); } } private void processMap(JVar mapSchemaVar, final String name, final Schema mapSchema, final Schema readerMapSchema, JBlock parentBody, FieldAction action, BiConsumer<JBlock, JExpression> putMapIntoParent) { if (action.getShouldRead()) { Symbol valuesActionSymbol = null; for (Symbol symbol : action.getSymbol().production) { if (Symbol.Kind.REPEATER.equals(symbol.kind) && "map-end".equals(getSymbolPrintName(((Symbol.Repeater) symbol).end))) { valuesActionSymbol = symbol; break; } } if (valuesActionSymbol == null) { throw new FastDeserializerGeneratorException("unable to determine action for map: " + name); } action = FieldAction.fromValues(mapSchema.getValueType().getType(), action.getShouldRead(), valuesActionSymbol); } else { action = FieldAction.fromValues(mapSchema.getValueType().getType(), false, EMPTY_SYMBOL); } final JVar mapVar = action.getShouldRead() ? declareValueVar(name, readerMapSchema, parentBody) : null; JVar chunkLen = parentBody.decl(codeModel.LONG, getVariableName("chunkLen"), JExpr.direct(DECODER + ".readMapStart()")); JConditional conditional = parentBody._if(chunkLen.gt(JExpr.lit(0))); JBlock ifBlock = conditional._then(); if (action.getShouldRead()) { ifBlock.assign(mapVar, JExpr._new(schemaAssistant.classFromSchema(readerMapSchema, false))); JBlock elseBlock = conditional._else(); elseBlock.assign(mapVar, codeModel.ref(Collections.class).staticInvoke("emptyMap")); } JDoLoop doLoop = ifBlock._do(chunkLen.gt(JExpr.lit(0))); JForLoop forLoop = doLoop.body()._for(); JVar counter = forLoop.init(codeModel.INT, getVariableName("counter"), JExpr.lit(0)); forLoop.test(counter.lt(chunkLen)); forLoop.update(counter.incr()); JBlock forBody = forLoop.body(); JClass keyClass = schemaAssistant.keyClassFromMapSchema(action.getShouldRead() ? readerMapSchema : mapSchema); JExpression keyValueExpression = (string.equals(keyClass)) ? JExpr.direct(DECODER + ".readString()") : JExpr.direct(DECODER + ".readString(null)"); if (SchemaAssistant.hasStringableKey(mapSchema)) { keyValueExpression = JExpr._new(keyClass).arg(keyValueExpression.invoke("toString")); } JVar key = forBody.decl(keyClass, getVariableName("key"), keyValueExpression); JVar mapValueSchemaVar = null; if (action.getShouldRead() && useGenericTypes) { mapValueSchemaVar = declareSchemaVar(mapSchema.getValueType(), name + "MapValueSchema", mapSchemaVar.invoke("getValueType")); } BiConsumer<JBlock, JExpression> putValueInMap = null; if (action.getShouldRead()) { putValueInMap = (block, expression) -> block.invoke(mapVar, "put").arg(key).arg(expression); } if (SchemaAssistant.isComplexType(mapSchema.getValueType())) { String valueName = name + "Value"; Schema readerMapValueSchema = null; if (action.getShouldRead()) { readerMapValueSchema = readerMapSchema.getValueType(); } processComplexType(mapValueSchemaVar, valueName, mapSchema.getValueType(), readerMapValueSchema, forBody, action, putValueInMap); } else { // to preserve reader string specific options use reader map schema if (action.getShouldRead() && Schema.Type.STRING.equals(mapSchema.getValueType().getType())) { processSimpleType(readerMapSchema.getValueType(), forBody, action, putValueInMap); } else { processSimpleType(mapSchema.getValueType(), forBody, action, putValueInMap); } } doLoop.body().assign(chunkLen, JExpr.direct(DECODER + ".mapNext()")); if (action.getShouldRead()) { putMapIntoParent.accept(parentBody, mapVar); } } private void processFixed(final Schema schema, JBlock body, FieldAction action, BiConsumer<JBlock, JExpression> putFixedIntoParent) { if (action.getShouldRead()) { JVar fixedBuffer = body.decl(codeModel.ref(byte[].class), getVariableName(schema.getName())) .init(JExpr.direct(" new byte[" + schema.getFixedSize() + "]")); body.directStatement(DECODER + ".readFixed(" + fixedBuffer.name() + ");"); JInvocation createFixed = JExpr._new(schemaAssistant.classFromSchema(schema)); if (useGenericTypes) createFixed = createFixed.arg(getSchemaExpr(schema)); putFixedIntoParent.accept(body, createFixed.arg(fixedBuffer)); } else { body.directStatement(DECODER + ".skipFixed(" + schema.getFixedSize() + ");"); } } private void processEnum(final Schema schema, final JBlock body, FieldAction action, BiConsumer<JBlock, JExpression> putEnumIntoParent) { if (action.getShouldRead()) { Symbol.EnumAdjustAction enumAdjustAction = null; if (action.getSymbol() instanceof Symbol.EnumAdjustAction) { enumAdjustAction = (Symbol.EnumAdjustAction) action.getSymbol(); } else { for (Symbol symbol : action.getSymbol().production) { if (symbol instanceof Symbol.EnumAdjustAction) { enumAdjustAction = (Symbol.EnumAdjustAction) symbol; } } } boolean enumOrderCorrect = true; for (int i = 0; i < enumAdjustAction.adjustments.length; i++) { Object adjustment = enumAdjustAction.adjustments[i]; if (adjustment instanceof String) { throw new FastDeserializerGeneratorException( schema.getName() + " enum label impossible to deserialize: " + adjustment.toString()); } else if (!adjustment.equals(i)) { enumOrderCorrect = false; } } JExpression newEnum; JExpression enumValueExpr = JExpr.direct(DECODER + ".readEnum()"); if (enumOrderCorrect) { newEnum = schemaAssistant.getEnumValueByIndex(schema, enumValueExpr, getSchemaExpr(schema)); } else { JVar enumIndex = body.decl(codeModel.INT, getVariableName("enumIndex"), enumValueExpr); JClass enumClass = schemaAssistant.classFromSchema(schema); newEnum = body.decl(enumClass, getVariableName("enumValue"), JExpr._null()); for (int i = 0; i < enumAdjustAction.adjustments.length; i++) { JExpression ithVal = schemaAssistant .getEnumValueByIndex(schema, JExpr.lit((Integer) enumAdjustAction.adjustments[i]), getSchemaExpr(schema)); body._if(enumIndex.eq(JExpr.lit(i)))._then().assign((JVar) newEnum, ithVal); } } putEnumIntoParent.accept(body, newEnum); } else { body.directStatement(DECODER + ".readEnum();"); } } private void processPrimitive(final Schema schema, JBlock body, FieldAction action, BiConsumer<JBlock, JExpression> putValueIntoParent) { String readFunction; switch (schema.getType()) { case STRING: if (action.getShouldRead()) { if (string.equals(schemaAssistant.classFromSchema(schema))) { readFunction = "readString()"; } else { // reads as Utf8 readFunction = "readString(null)"; } } else { readFunction = "skipString()"; } break; case BYTES: readFunction = "readBytes(null)"; break; case INT: readFunction = "readInt()"; break; case LONG: readFunction = "readLong()"; break; case FLOAT: readFunction = "readFloat()"; break; case DOUBLE: readFunction = "readDouble()"; break; case BOOLEAN: readFunction = "readBoolean()"; break; default: throw new FastDeserializerGeneratorException( "Unsupported primitive schema of type: " + schema.getType()); } JExpression primitiveValueExpression = JExpr.direct("decoder." + readFunction); if (action.getShouldRead()) { if (schema.getType().equals(Schema.Type.STRING) && SchemaAssistant.isStringable(schema)) { primitiveValueExpression = JExpr._new(schemaAssistant.classFromSchema(schema)) .arg(primitiveValueExpression.invoke("toString")); } putValueIntoParent.accept(body, primitiveValueExpression); } else { body.directStatement(DECODER + "." + readFunction + ";"); } } private JVar declareSchemaVariableForRecordField(final String name, final Schema schema, JVar schemaVar) { return declareSchemaVar(schema, name + "Field", schemaVar.invoke("getField").arg(name).invoke("schema")); } private JVar declareValueVar(final String name, final Schema schema, JBlock block) { if (SchemaAssistant.isComplexType(schema)) { return block.decl(schemaAssistant.classFromSchema(schema), getVariableName(StringUtils.uncapitalize(name)), JExpr._null()); } else { throw new FastDeserializerGeneratorException("Only complex types allowed!"); } } private JVar declareSchemaVar(Schema valueSchema, String variableName, JInvocation getValueType) { if (!useGenericTypes) { return null; } if (SchemaAssistant.isComplexType(valueSchema) || Schema.Type.ENUM.equals(valueSchema.getType())) { int schemaId = getSchemaId(valueSchema); if (schemaVarMap.get(schemaId) != null) { return schemaVarMap.get(schemaId); } else { JVar schemaVar = schemaMapMethod.body().decl(codeModel.ref(Schema.class), getVariableName(StringUtils.uncapitalize(variableName)), getValueType); registerSchema(valueSchema, schemaId, schemaVar); schemaVarMap.put(schemaId, schemaVar); return schemaVar; } } else { return null; } } private void registerSchema(final Schema writerSchema, JVar schemaVar) { registerSchema(writerSchema, getSchemaId(writerSchema), schemaVar); } private void registerSchema(final Schema writerSchema, int schemaId, JVar schemaVar) { if ((Schema.Type.RECORD.equals(writerSchema.getType()) || Schema.Type.ENUM.equals(writerSchema.getType()) || Schema.Type.ARRAY.equals(writerSchema.getType())) && schemaNotRegistered(writerSchema)) { schemaMap.put(schemaId, writerSchema); schemaMapMethod.body().invoke(schemaMapField, "put").arg(JExpr.lit(schemaId)).arg(schemaVar); } } private boolean schemaNotRegistered(final Schema schema) { return !schemaMap.containsKey(getSchemaId(schema)); } private boolean methodAlreadyDefined(final Schema schema, boolean read) { if (!Schema.Type.RECORD.equals(schema.getType())) { throw new FastDeserializerGeneratorException( "Methods are defined only for records, not for " + schema.getType()); } return (read ? deserializeMethodMap : skipMethodMap).containsKey(schema.getFullName()); } private JMethod getMethod(final Schema schema, boolean read) { if (!Schema.Type.RECORD.equals(schema.getType())) { throw new FastDeserializerGeneratorException( "Methods are defined only for records, not for " + schema.getType()); } if (!methodAlreadyDefined(schema, read)) { throw new FastDeserializerGeneratorException("No method for schema: " + schema.getFullName()); } return (read ? deserializeMethodMap : skipMethodMap).get(schema.getFullName()); } private JMethod createMethod(final Schema schema, boolean read) { if (!Schema.Type.RECORD.equals(schema.getType())) { throw new FastDeserializerGeneratorException( "Methods are defined only for records, not for " + schema.getType()); } if (methodAlreadyDefined(schema, read)) { throw new FastDeserializerGeneratorException("Method already exists for: " + schema.getFullName()); } JMethod method = deserializerClass.method(JMod.PUBLIC, read ? schemaAssistant.classFromSchema(schema) : codeModel.VOID, getVariableName("deserialize" + schema.getName())); method._throws(IOException.class); method.param(Decoder.class, DECODER); (read ? deserializeMethodMap : skipMethodMap).put(schema.getFullName(), method); return method; } private JInvocation getSchemaExpr(Schema schema) { return useGenericTypes ? schemaMapField.invoke("get").arg(JExpr.lit(getSchemaId(schema))) : null; } }