uk.co.real_logic.sbe.generation.java.JavaDtoGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sbe-tool Show documentation
Show all versions of sbe-tool Show documentation
FIX/SBE - OSI layer 6 presentation for encoding and decoding application messages in binary format for low-latency applications.
The newest version!
/*
* Copyright 2013-2024 Real Logic Limited.
* Copyright (C) 2017 MarketFactory, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package uk.co.real_logic.sbe.generation.java;
import uk.co.real_logic.sbe.PrimitiveType;
import uk.co.real_logic.sbe.generation.CodeGenerator;
import uk.co.real_logic.sbe.generation.Generators;
import uk.co.real_logic.sbe.ir.Ir;
import uk.co.real_logic.sbe.ir.Signal;
import uk.co.real_logic.sbe.ir.Token;
import org.agrona.LangUtil;
import org.agrona.Verify;
import org.agrona.generation.DynamicPackageOutputManager;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import static uk.co.real_logic.sbe.generation.Generators.toLowerFirstChar;
import static uk.co.real_logic.sbe.generation.Generators.toUpperFirstChar;
import static uk.co.real_logic.sbe.generation.java.JavaUtil.*;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectFields;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectGroups;
import static uk.co.real_logic.sbe.ir.GenerationUtil.collectVarData;
/**
* DTO generator for the Java programming language.
*/
public class JavaDtoGenerator implements CodeGenerator
{
private static final Predicate ALWAYS_FALSE_PREDICATE = ignored -> false;
private static final String INDENT = " ";
private static final String BASE_INDENT = "";
private final Ir ir;
private final DynamicPackageOutputManager outputManager;
private final boolean shouldSupportTypesPackageNames;
private final Set packageNameByTypes = new HashSet<>();
/**
* Create a new Java DTO {@link CodeGenerator}.
*
* @param ir for the messages and types.
* @param shouldSupportTypesPackageNames generator support for types in their own package.
* @param outputManager for generating the DTOs to.
*/
public JavaDtoGenerator(
final Ir ir,
final boolean shouldSupportTypesPackageNames,
final DynamicPackageOutputManager outputManager)
{
this.shouldSupportTypesPackageNames = shouldSupportTypesPackageNames;
Verify.notNull(ir, "ir");
Verify.notNull(outputManager, "outputManager");
this.ir = ir;
this.outputManager = outputManager;
}
/**
* Fetch the type's explicit package - if set and should be supported.
*
* @param token the 0-th token of the type.
* @param ir the intermediate representation.
* @return the overridden package name of the type if set and supported, or {@link Ir#applicableNamespace()}.
*/
private String fetchTypesPackageName(final Token token, final Ir ir)
{
if (shouldSupportTypesPackageNames && token.packageName() != null)
{
outputManager.setPackageName(token.packageName());
return token.packageName();
}
return ir.applicableNamespace();
}
/**
* {@inheritDoc}
*/
public void generate() throws IOException
{
generateDtosForTypes();
for (final List tokens : ir.messages())
{
final Token msgToken = tokens.get(0);
final String encoderClassName = encoderName(msgToken.name());
final String decoderClassName = decoderName(msgToken.name());
final String dtoClassName = formatDtoClassName(msgToken.name());
final List messageBody = tokens.subList(1, tokens.size() - 1);
int offset = 0;
final ClassBuilder classBuilder = new ClassBuilder(dtoClassName, BASE_INDENT, "public final");
final List fields = new ArrayList<>();
offset = collectFields(messageBody, offset, fields);
generateFields(classBuilder, decoderClassName, fields, BASE_INDENT + INDENT);
final List groups = new ArrayList<>();
offset = collectGroups(messageBody, offset, groups);
generateGroups(classBuilder, dtoClassName, encoderClassName, decoderClassName, groups,
BASE_INDENT + INDENT);
final List varData = new ArrayList<>();
collectVarData(messageBody, offset, varData);
generateVarData(classBuilder, varData, BASE_INDENT + INDENT);
generateDecodeWith(classBuilder, dtoClassName, decoderClassName, fields,
groups, varData, BASE_INDENT + INDENT, fieldToken -> fieldToken.version() > msgToken.version());
generateDecodeFrom(classBuilder, dtoClassName, decoderClassName, BASE_INDENT + INDENT);
generateEncodeWith(classBuilder, dtoClassName, encoderClassName, fields, groups, varData,
BASE_INDENT + INDENT);
generateEncodeWithOverloads(classBuilder, dtoClassName, encoderClassName, BASE_INDENT + INDENT);
generateComputeEncodedLength(classBuilder, decoderClassName,
decoderClassName + ".BLOCK_LENGTH",
groups, varData, BASE_INDENT + INDENT);
generateDisplay(classBuilder, encoderClassName, "computeEncodedLength()",
BASE_INDENT + INDENT);
final String packageName = fetchTypesPackageName(msgToken, ir);
try (Writer out = outputManager.createOutput(dtoClassName))
{
out.append(generateDtoFileHeader(packageName));
out.append("import org.agrona.DirectBuffer;\n");
out.append("import org.agrona.MutableDirectBuffer;\n");
out.append("import org.agrona.concurrent.UnsafeBuffer;\n\n");
out.append("import java.util.ArrayList;\n");
out.append("import java.util.List;\n\n");
out.append(generateDocumentation(BASE_INDENT, msgToken));
classBuilder.appendTo(out);
}
}
}
private static final class ClassBuilder
{
private final StringBuilder fieldSb = new StringBuilder();
private final StringBuilder privateSb = new StringBuilder();
private final StringBuilder publicSb = new StringBuilder();
private final String className;
private final String indent;
private final String modifiers;
private ClassBuilder(
final String className,
final String indent,
final String modifiers)
{
this.className = className;
this.indent = indent;
this.modifiers = modifiers.length() == 0 ? modifiers : modifiers + " ";
}
public StringBuilder appendField()
{
return fieldSb;
}
public StringBuilder appendPrivate()
{
return privateSb;
}
public StringBuilder appendPublic()
{
return publicSb;
}
public void appendTo(final Appendable out)
{
try
{
out.append(indent).append(modifiers).append("class ").append(className).append("\n")
.append(indent).append("{\n")
.append(fieldSb)
.append(privateSb)
.append(publicSb)
.append(indent).append("}\n");
}
catch (final IOException exception)
{
LangUtil.rethrowUnchecked(exception);
}
}
}
private void generateGroups(
final ClassBuilder classBuilder,
final String qualifiedParentDtoClassName,
final String qualifiedParentEncoderClassName,
final String qualifiedParentDecoderClassName,
final List tokens,
final String indent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token groupToken = tokens.get(i);
if (groupToken.signal() != Signal.BEGIN_GROUP)
{
throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
}
final String groupName = groupToken.name();
final String groupClassName = formatDtoClassName(groupName);
final String qualifiedDtoClassName = qualifiedParentDtoClassName + "." + groupClassName;
final Token dimToken = tokens.get(i + 1);
if (dimToken.signal() != Signal.BEGIN_COMPOSITE)
{
throw new IllegalStateException("groups must start with BEGIN_COMPOSITE: token=" + dimToken);
}
final int sinceVersion = dimToken.version();
final String fieldName = formatFieldName(groupName);
final String formattedPropertyName = formatPropertyName(groupName);
classBuilder.appendField().append(indent).append("private List<")
.append(qualifiedDtoClassName).append("> ")
.append(fieldName).append(" = new ArrayList<>();\n");
final ClassBuilder groupClassBuilder = new ClassBuilder(groupClassName, indent, "public static final");
i++;
i += tokens.get(i).componentTokenCount();
final String qualifiedEncoderClassName =
qualifiedParentEncoderClassName + "." + encoderName(groupName);
final String qualifiedDecoderClassName =
qualifiedParentDecoderClassName + "." + decoderName(groupName);
final List fields = new ArrayList<>();
i = collectFields(tokens, i, fields);
generateFields(groupClassBuilder, qualifiedDecoderClassName, fields, indent + INDENT);
final List groups = new ArrayList<>();
i = collectGroups(tokens, i, groups);
generateGroups(groupClassBuilder, qualifiedDtoClassName,
qualifiedEncoderClassName, qualifiedDecoderClassName, groups, indent + INDENT);
final List varData = new ArrayList<>();
i = collectVarData(tokens, i, varData);
generateVarData(groupClassBuilder, varData, indent + INDENT);
final Predicate wasAddedAfterGroup = token ->
{
final boolean addedAfterParent = token.version() > sinceVersion;
if (addedAfterParent && token.signal() == Signal.BEGIN_VAR_DATA)
{
throw new IllegalStateException("Cannot extend var data inside a group.");
}
return addedAfterParent;
};
generateDecodeListWith(
groupClassBuilder, groupClassName, qualifiedDecoderClassName, indent + INDENT);
generateDecodeWith(groupClassBuilder, groupClassName, qualifiedDecoderClassName,
fields, groups, varData, indent + INDENT, wasAddedAfterGroup);
generateEncodeWith(
groupClassBuilder, groupClassName, qualifiedEncoderClassName, fields, groups, varData, indent + INDENT);
generateComputeEncodedLength(groupClassBuilder, qualifiedDecoderClassName,
qualifiedDecoderClassName + ".sbeBlockLength()",
groups, varData, indent + INDENT);
groupClassBuilder.appendTo(
classBuilder.appendPublic().append("\n").append(generateDocumentation(indent, groupToken))
);
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, groupToken))
.append(indent).append("public List<").append(qualifiedDtoClassName).append("> ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, groupToken))
.append(indent).append("public void ").append(formattedPropertyName).append("(")
.append("List<").append(qualifiedDtoClassName).append("> value)")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
}
}
private void generateComputeEncodedLength(
final ClassBuilder classBuilder,
final String qualifiedDecoderClassName,
final String blockLengthExpression,
final List groupTokens,
final List varDataTokens,
final String indent)
{
final StringBuilder lengthBuilder = classBuilder.appendPublic()
.append("\n")
.append(indent).append("public int computeEncodedLength()\n")
.append(indent).append("{\n");
lengthBuilder
.append(indent).append(INDENT).append("int encodedLength = 0;\n");
lengthBuilder.append(indent).append(INDENT).append("encodedLength += ").append(blockLengthExpression)
.append(";\n\n");
for (int i = 0, size = groupTokens.size(); i < size; i++)
{
final Token groupToken = groupTokens.get(i);
if (groupToken.signal() != Signal.BEGIN_GROUP)
{
throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
}
i++;
i += groupTokens.get(i).componentTokenCount();
final List fields = new ArrayList<>();
i = collectFields(groupTokens, i, fields);
final List subGroups = new ArrayList<>();
i = collectGroups(groupTokens, i, subGroups);
final List subVarData = new ArrayList<>();
i = collectVarData(groupTokens, i, subVarData);
final String groupName = groupToken.name();
final String fieldName = formatFieldName(groupName);
final String groupDecoderClassName = qualifiedDecoderClassName + "." + decoderName(groupName);
final String groupDtoClassName = formatDtoClassName(groupName);
lengthBuilder
.append(indent).append(INDENT).append("encodedLength += ")
.append(groupDecoderClassName).append(".sbeHeaderSize();\n\n")
.append(indent).append(INDENT).append("for (").append(groupDtoClassName).append(" group : ")
.append(fieldName).append(")\n")
.append(indent).append(INDENT).append("{\n")
.append(indent).append(INDENT).append(INDENT)
.append("encodedLength += group.computeEncodedLength();\n")
.append(indent).append(INDENT).append("}\n\n");
}
for (int i = 0, size = varDataTokens.size(); i < size; i++)
{
final Token token = varDataTokens.get(i);
if (token.signal() == Signal.BEGIN_VAR_DATA)
{
final String propertyName = token.name();
final Token varDataToken = Generators.findFirst("varData", varDataTokens, i);
final String fieldName = formatFieldName(propertyName);
lengthBuilder.append(indent).append(INDENT).append("encodedLength += ")
.append(qualifiedDecoderClassName).append(".")
.append(formatPropertyName(propertyName)).append("HeaderLength();\n");
final String characterEncoding = varDataToken.encoding().characterEncoding();
final String lengthAccessor = characterEncoding == null ? ".length" : ".length()";
lengthBuilder.append(indent).append(INDENT).append("encodedLength += ")
.append(fieldName).append(lengthAccessor);
final int elementByteLength = varDataToken.encoding().primitiveType().size();
if (elementByteLength != 1)
{
lengthBuilder.append(" * ").append(elementByteLength);
}
lengthBuilder.append(";\n\n");
}
}
lengthBuilder.append(indent).append(INDENT).append("return encodedLength;\n")
.append(indent).append("}\n");
}
private void generateCompositeDecodeWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String decoderClassName,
final List tokens,
final String indent)
{
final StringBuilder decodeBuilder = classBuilder.appendPublic().append("\n")
.append(indent).append("public static void decodeWith(").append(decoderClassName).append(" decoder, ")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n");
for (int i = 0; i < tokens.size(); )
{
final Token token = tokens.get(i);
generateFieldDecodeWith(
decodeBuilder, token, token, decoderClassName, indent + INDENT, ALWAYS_FALSE_PREDICATE);
i += tokens.get(i).componentTokenCount();
}
decodeBuilder.append(indent).append("}\n");
}
private void generateCompositeEncodeWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String encoderClassName,
final List tokens,
final String indent)
{
final StringBuilder encodeBuilder = classBuilder.appendPublic().append("\n")
.append(indent).append("public static void encodeWith(").append(encoderClassName).append(" encoder, ")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n");
for (int i = 0; i < tokens.size(); )
{
final Token token = tokens.get(i);
generateFieldEncodeWith(encodeBuilder, token, token, indent + INDENT);
i += tokens.get(i).componentTokenCount();
}
encodeBuilder.append(indent).append("}\n");
}
private void generateDecodeListWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String decoderClassName,
final String indent)
{
classBuilder.appendPublic().append("\n")
.append(indent).append("public static List<").append(dtoClassName).append("> decodeManyWith(")
.append(decoderClassName).append(" decoder)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("List<").append(dtoClassName)
.append("> dtos = new ArrayList<>(decoder.count());\n")
.append(indent).append(INDENT)
.append("while (decoder.hasNext())\n")
.append(indent).append(INDENT)
.append("{\n")
.append(indent).append(INDENT).append(INDENT)
.append(dtoClassName).append(" dto = new ").append(dtoClassName).append("();\n")
.append(indent).append(INDENT).append(INDENT)
.append(dtoClassName).append(".decodeWith(decoder.next(), dto);\n")
.append(indent).append(INDENT).append(INDENT)
.append("dtos.add(dto);\n")
.append(indent).append(INDENT)
.append("}\n")
.append(indent).append(INDENT)
.append("return dtos;\n")
.append(indent).append("}\n");
}
private void generateDecodeWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String decoderClassName,
final List fields,
final List groups,
final List varData,
final String indent,
final Predicate wasAddedAfterParent)
{
final StringBuilder decodeBuilder = classBuilder.appendPublic().append("\n")
.append(indent).append("public static void decodeWith(").append(decoderClassName).append(" decoder, ")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n");
generateMessageFieldsDecodeWith(decodeBuilder, fields, decoderClassName, indent + INDENT, wasAddedAfterParent);
generateGroupsDecodeWith(decodeBuilder, groups, indent + INDENT);
generateVarDataDecodeWith(decodeBuilder, decoderClassName, varData, indent + INDENT, wasAddedAfterParent);
decodeBuilder.append(indent).append("}\n");
}
private static void generateDecodeFrom(
final ClassBuilder classBuilder,
final String dtoClassName,
final String decoderClassName,
final String indent)
{
classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static ").append(dtoClassName).append(" decodeFrom(")
.append("DirectBuffer buffer, int offset, ")
.append("int actingBlockLength, int actingVersion)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(decoderClassName).append(" decoder = new ")
.append(decoderClassName).append("();\n")
.append(indent).append(INDENT)
.append("decoder.wrap(buffer, offset, actingBlockLength, actingVersion);\n")
.append(indent).append(INDENT).append(dtoClassName).append(" dto = new ")
.append(dtoClassName).append("();\n")
.append(indent).append(INDENT).append("decodeWith(decoder, dto);\n")
.append(indent).append(INDENT).append("return dto;\n")
.append(indent).append("}\n");
}
private void generateMessageFieldsDecodeWith(
final StringBuilder sb,
final List tokens,
final String decoderClassName,
final String indent,
final Predicate wasAddedAfterParent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token signalToken = tokens.get(i);
if (signalToken.signal() == Signal.BEGIN_FIELD)
{
final Token encodingToken = tokens.get(i + 1);
generateFieldDecodeWith(sb, signalToken, encodingToken, decoderClassName, indent, wasAddedAfterParent);
}
}
}
private void generateFieldDecodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String decoderClassName,
final String indent,
final Predicate wasAddedAfterParent)
{
switch (typeToken.signal())
{
case ENCODING:
generatePrimitiveDecodeWith(sb, fieldToken, typeToken, decoderClassName, indent, wasAddedAfterParent);
break;
case BEGIN_SET:
final String bitSetName = formatDtoClassName(typeToken.applicableTypeName());
generateBitSetDecodeWith(sb, decoderClassName, fieldToken, bitSetName, indent, wasAddedAfterParent);
break;
case BEGIN_ENUM:
generateEnumDecodeWith(sb, fieldToken, indent);
break;
case BEGIN_COMPOSITE:
generateCompositePropertyDecodeWith(sb, fieldToken, typeToken, indent);
break;
default:
break;
}
}
private void generatePrimitiveDecodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String decoderClassName,
final String indent,
final Predicate wasAddedAfterParent)
{
if (typeToken.isConstantEncoding())
{
return;
}
final int arrayLength = typeToken.arrayLength();
if (arrayLength == 1)
{
final String decoderNullValue =
decoderClassName + "." + formatPropertyName(fieldToken.name()) + "NullValue()";
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
generateRecordPropertyAssignment(
sb,
fieldToken,
indent,
"decoder.actingVersion() >= " + decoderClassName + "." + formattedPropertyName + "SinceVersion()",
"decoder." + formattedPropertyName + "()",
decoderNullValue,
wasAddedAfterParent
);
}
else if (arrayLength > 1)
{
generateArrayDecodeWith(sb, decoderClassName, fieldToken, typeToken, indent, wasAddedAfterParent);
}
}
private void generateArrayDecodeWith(
final StringBuilder sb,
final String decoderClassName,
final Token fieldToken,
final Token typeToken,
final String indent,
final Predicate wasAddedAfterParent)
{
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final PrimitiveType primitiveType = typeToken.encoding().primitiveType();
if (primitiveType == PrimitiveType.CHAR)
{
generateRecordPropertyAssignment(
sb,
fieldToken,
indent,
"decoder.actingVersion() >= " + decoderClassName + "." + formattedPropertyName + "SinceVersion()",
"decoder." + formattedPropertyName + "()",
"\"\"",
wasAddedAfterParent
);
}
else
{
final StringBuilder initializerList = new StringBuilder();
final String elementType = javaTypeName(primitiveType);
initializerList.append("new ").append(elementType).append("[] { ");
final int arrayLength = typeToken.arrayLength();
for (int i = 0; i < arrayLength; i++)
{
initializerList.append("decoder.").append(formattedPropertyName).append("(").append(i).append("),");
}
assert arrayLength > 0;
initializerList.setLength(initializerList.length() - 1);
initializerList.append(" }");
generateRecordPropertyAssignment(
sb,
fieldToken,
indent,
"decoder.actingVersion() >= " + decoderClassName + "." + formattedPropertyName + "SinceVersion()",
initializerList,
"new " + elementType + "[0]",
wasAddedAfterParent
);
}
}
private void generateBitSetDecodeWith(
final StringBuilder sb,
final String decoderClassName,
final Token fieldToken,
final String dtoTypeName,
final String indent,
final Predicate wasAddedAfterParent)
{
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
if (wasAddedAfterParent.test(fieldToken))
{
sb.append(indent).append("if (decoder.actingVersion() >= ")
.append(decoderClassName).append(".")
.append(formattedPropertyName).append("SinceVersion())\n")
.append(indent).append("{\n");
sb.append(indent).append(INDENT).append(dtoTypeName).append(".decodeWith(decoder.")
.append(formattedPropertyName).append("(), ")
.append("dto.").append(formattedPropertyName).append("());\n");
sb.append(indent).append("}\n")
.append(indent).append("else\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("dto.").append(formattedPropertyName).append("().clear();\n")
.append(indent).append("}\n");
}
else
{
sb.append(indent).append(dtoTypeName).append(".decodeWith(decoder.")
.append(formattedPropertyName).append("(), ")
.append("dto.").append(formattedPropertyName).append("());\n");
}
}
private void generateEnumDecodeWith(
final StringBuilder sb,
final Token fieldToken,
final String indent)
{
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
sb.append(indent).append("dto.").append(formattedPropertyName).append("(")
.append("decoder.").append(formattedPropertyName).append("());\n");
}
private void generateCompositePropertyDecodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final String dtoClassName = formatDtoClassName(typeToken.applicableTypeName());
sb.append(indent).append(dtoClassName).append(".decodeWith(decoder.")
.append(formattedPropertyName).append("(), ")
.append("dto.").append(formattedPropertyName).append("());\n");
}
private void generateGroupsDecodeWith(
final StringBuilder sb,
final List tokens,
final String indent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token groupToken = tokens.get(i);
if (groupToken.signal() != Signal.BEGIN_GROUP)
{
throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
}
final String groupName = groupToken.name();
final String formattedPropertyName = formatPropertyName(groupName);
final String groupDtoClassName = formatDtoClassName(groupName);
sb.append(indent).append("dto.").append(formattedPropertyName).append("(")
.append(groupDtoClassName).append(".decodeManyWith(decoder.")
.append(formattedPropertyName).append("()));\n");
i++;
i += tokens.get(i).componentTokenCount();
final List fields = new ArrayList<>();
i = collectFields(tokens, i, fields);
final List groups = new ArrayList<>();
i = collectGroups(tokens, i, groups);
final List varData = new ArrayList<>();
i = collectVarData(tokens, i, varData);
}
}
private void generateVarDataDecodeWith(
final StringBuilder sb,
final String decoderClassName,
final List tokens,
final String indent,
final Predicate wasAddedAfterParent)
{
for (int i = 0; i < tokens.size(); i++)
{
final Token token = tokens.get(i);
if (token.signal() == Signal.BEGIN_VAR_DATA)
{
final String propertyName = token.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final Token varDataToken = Generators.findFirst("varData", tokens, i);
final String characterEncoding = varDataToken.encoding().characterEncoding();
final boolean isOptional = wasAddedAfterParent.test(token);
final String blockIndent = isOptional ? indent + INDENT : indent;
final String dataVar = toLowerFirstChar(propertyName) + "Data";
final StringBuilder decoderValueExtraction = new StringBuilder();
if (characterEncoding == null)
{
decoderValueExtraction.append(blockIndent).append("byte[] ").append(dataVar)
.append(" = new byte[decoder.").append(formattedPropertyName).append("Length()];\n")
.append(blockIndent).append("decoder.get").append(toUpperFirstChar(formattedPropertyName))
.append("(").append(dataVar).append(", 0, decoder.").append(formattedPropertyName)
.append("Length());\n");
}
else
{
decoderValueExtraction.append(blockIndent).append("String ").append(dataVar)
.append(" = decoder.").append(formattedPropertyName).append("();\n");
}
if (isOptional)
{
sb.append(indent).append("if (decoder.actingVersion() >= ")
.append(decoderClassName).append(".")
.append(formattedPropertyName).append("SinceVersion())\n")
.append(indent).append("{\n");
sb.append(decoderValueExtraction);
sb.append(indent).append(INDENT).append("dto.").append(formattedPropertyName).append("(")
.append(dataVar).append(");\n");
final String nullDtoValue = characterEncoding == null ? "new byte[0]" : "\"\"";
sb.append(indent).append("}\n")
.append(indent).append("else\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("dto.")
.append(formattedPropertyName).append("(").append(nullDtoValue).append(");\n")
.append(indent).append("}\n");
}
else
{
sb.append(decoderValueExtraction);
sb.append(indent).append("dto.").append(formattedPropertyName).append("(")
.append(dataVar).append(");\n");
}
}
}
}
private void generateRecordPropertyAssignment(
final StringBuilder sb,
final Token token,
final String indent,
final String presenceExpression,
final CharSequence getExpression,
final String nullDecoderValue,
final Predicate wasAddedAfterParent)
{
final String propertyName = token.name();
final String formattedPropertyName = formatPropertyName(propertyName);
if (wasAddedAfterParent.test(token))
{
sb.append(indent).append("if (").append(presenceExpression).append(")\n")
.append(indent).append("{\n");
sb.append(indent).append(INDENT).append("dto.").append(formattedPropertyName).append("(")
.append(getExpression).append(");\n");
sb.append(indent).append("}\n")
.append(indent).append("else\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("dto.").append(formattedPropertyName).append("(")
.append(nullDecoderValue).append(");\n")
.append(indent).append("}\n");
}
else
{
sb.append(indent).append("dto.").append(formattedPropertyName).append("(")
.append(getExpression).append(");\n");
}
}
private void generateEncodeWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String encoderClassName,
final List fields,
final List groups,
final List varData,
final String indent)
{
final StringBuilder encodeBuilder = classBuilder.appendPublic().append("\n")
.append(indent).append("public static void encodeWith(").append(encoderClassName).append(" encoder, ")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n");
generateFieldsEncodeWith(encodeBuilder, fields, indent + INDENT);
generateGroupsEncodeWith(encodeBuilder, encoderClassName, groups, indent + INDENT);
generateVarDataEncodeWith(encodeBuilder, varData, indent + INDENT);
encodeBuilder.append(indent).append("}\n");
}
private static void generateEncodeWithOverloads(
final ClassBuilder classBuilder,
final String dtoClassName,
final String encoderClassName,
final String indent)
{
classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static int encodeWith(").append(dtoClassName).append(" dto, ")
.append("MutableDirectBuffer buffer, int offset)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(encoderClassName).append(" encoder = new ")
.append(encoderClassName).append("();\n")
.append(indent).append(INDENT).append("encoder.wrap(buffer, offset);\n")
.append(indent).append(INDENT).append("encodeWith(encoder, dto);\n")
.append(indent).append(INDENT).append("return encoder.encodedLength();\n")
.append(indent).append("}\n");
classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static int encodeWithHeaderWith(")
.append(dtoClassName).append(" dto, ")
.append("MutableDirectBuffer buffer, int offset)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(encoderClassName).append(" encoder = new ")
.append(encoderClassName).append("();\n")
.append(indent).append(INDENT)
.append("encoder.wrapAndApplyHeader(buffer, offset, new MessageHeaderEncoder());\n")
.append(indent).append(INDENT).append("encodeWith(encoder, dto);\n")
.append(indent).append(INDENT).append("return encoder.limit() - offset;\n")
.append(indent).append("}\n");
classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static byte[] bytes(")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("byte[] bytes = new byte[dto.computeEncodedLength()];\n")
.append(indent).append(INDENT).append("encodeWith(dto, new UnsafeBuffer(bytes), 0);\n")
.append(indent).append(INDENT).append("return bytes;\n")
.append(indent).append("}\n");
classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static byte[] bytesWithHeader(")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("byte[] bytes = new byte[dto.computeEncodedLength() + ")
.append("MessageHeaderEncoder.ENCODED_LENGTH];\n")
.append(indent).append(INDENT).append("encodeWithHeaderWith(dto, new UnsafeBuffer(bytes), 0);\n")
.append(indent).append(INDENT).append("return bytes;\n")
.append(indent).append("}\n");
}
private void generateFieldsEncodeWith(
final StringBuilder sb,
final List tokens,
final String indent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token signalToken = tokens.get(i);
if (signalToken.signal() == Signal.BEGIN_FIELD)
{
final Token encodingToken = tokens.get(i + 1);
generateFieldEncodeWith(sb, signalToken, encodingToken, indent);
}
}
}
private void generateFieldEncodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String indent)
{
switch (typeToken.signal())
{
case ENCODING:
generatePrimitiveEncodeWith(sb, fieldToken, typeToken, indent);
break;
case BEGIN_ENUM:
generateEnumEncodeWith(sb, fieldToken, indent);
break;
case BEGIN_SET:
case BEGIN_COMPOSITE:
generateComplexPropertyEncodeWith(sb, fieldToken, typeToken, indent);
break;
default:
break;
}
}
private void generatePrimitiveEncodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String indent)
{
if (typeToken.isConstantEncoding())
{
return;
}
final int arrayLength = typeToken.arrayLength();
if (arrayLength == 1)
{
generatePrimitiveValueEncodeWith(sb, fieldToken, indent);
}
else if (arrayLength > 1)
{
generateArrayEncodeWith(sb, fieldToken, typeToken, indent);
}
}
private void generateArrayEncodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String indent)
{
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final PrimitiveType primitiveType = typeToken.encoding().primitiveType();
if (primitiveType == PrimitiveType.CHAR)
{
sb.append(indent).append("encoder.").append(toLowerFirstChar(propertyName)).append("(")
.append("dto.").append(formattedPropertyName).append("());\n");
}
else
{
final String javaTypeName = javaTypeName(primitiveType);
sb.append(indent).append(javaTypeName).append("[] ").append(formattedPropertyName).append(" = ")
.append("dto.").append(formattedPropertyName).append("();\n")
.append(indent).append("for (int i = 0; i < ").append(formattedPropertyName).append(".length; i++)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("encoder.").append(formattedPropertyName).append("(")
.append("i, ").append(formattedPropertyName).append("[i]);\n")
.append(indent).append("}\n");
}
}
private void generatePrimitiveValueEncodeWith(
final StringBuilder sb,
final Token fieldToken,
final String indent)
{
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final String accessor = "dto." + formattedPropertyName + "()";
sb.append(indent).append("encoder.").append(formattedPropertyName).append("(")
.append(accessor).append(");\n");
}
private void generateEnumEncodeWith(
final StringBuilder sb,
final Token fieldToken,
final String indent)
{
if (fieldToken.isConstantEncoding())
{
return;
}
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
sb.append(indent).append("encoder.").append(formattedPropertyName).append("(dto.")
.append(formattedPropertyName).append("());\n");
}
private void generateComplexPropertyEncodeWith(
final StringBuilder sb,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final String propertyName = fieldToken.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final String typeName = formatDtoClassName(typeToken.applicableTypeName());
sb.append(indent).append(typeName).append(".encodeWith(encoder.")
.append(formattedPropertyName).append("(), dto.")
.append(formattedPropertyName).append("());\n");
}
private void generateGroupsEncodeWith(
final StringBuilder sb,
final String parentEncoderClassName,
final List tokens,
final String indent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token groupToken = tokens.get(i);
if (groupToken.signal() != Signal.BEGIN_GROUP)
{
throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
}
final String groupName = groupToken.name();
final String formattedPropertyName = formatPropertyName(groupName);
final String groupEncoderVarName = groupName + "Encoder";
final String groupDtoTypeName = formatDtoClassName(groupName);
final String groupEncoderTypeName = parentEncoderClassName + "." + encoderName(groupName);
sb.append("\n")
.append(indent).append("List<").append(groupDtoTypeName).append("> ")
.append(formattedPropertyName).append(" = dto.").append(formattedPropertyName).append("();\n\n")
.append(indent).append(groupEncoderTypeName).append(" ").append(groupEncoderVarName)
.append(" = encoder.").append(formattedPropertyName)
.append("Count(").append(formattedPropertyName).append(".size());\n\n")
.append(indent).append("for (").append(groupDtoTypeName).append(" group : ")
.append(formattedPropertyName).append(")\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(groupDtoTypeName)
.append(".encodeWith(").append(groupEncoderVarName).append(".next(), group);\n")
.append(indent).append("}\n\n");
i++;
i += tokens.get(i).componentTokenCount();
final List fields = new ArrayList<>();
i = collectFields(tokens, i, fields);
final List groups = new ArrayList<>();
i = collectGroups(tokens, i, groups);
final List varData = new ArrayList<>();
i = collectVarData(tokens, i, varData);
}
}
private void generateVarDataEncodeWith(
final StringBuilder sb,
final List tokens,
final String indent)
{
for (int i = 0; i < tokens.size(); i++)
{
final Token token = tokens.get(i);
if (token.signal() == Signal.BEGIN_VAR_DATA)
{
final String propertyName = token.name();
final String formattedPropertyName = formatPropertyName(propertyName);
final Token varDataToken = Generators.findFirst("varData", tokens, i);
final String characterEncoding = varDataToken.encoding().characterEncoding();
if (characterEncoding == null)
{
sb.append(indent).append("encoder.put").append(toUpperFirstChar(propertyName)).append("(")
.append("dto.").append(formattedPropertyName).append("(),")
.append("0,")
.append("dto.").append(formattedPropertyName).append("().length);\n");
}
else
{
sb.append(indent).append("encoder.").append(formattedPropertyName).append("(")
.append("dto.").append(formattedPropertyName).append("());\n");
}
}
}
}
private void generateDisplay(
final ClassBuilder classBuilder,
final String encoderClassName,
final String lengthExpression,
final String indent)
{
final StringBuilder sb = classBuilder.appendPublic();
sb.append("\n")
.append(indent).append("public String toString()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT)
.append("MutableDirectBuffer buffer = new UnsafeBuffer(new byte[").append(lengthExpression).append("]);\n")
.append(indent).append(INDENT).append(encoderClassName).append(" encoder = new ")
.append(encoderClassName).append("();\n")
.append(indent).append(INDENT).append("encoder.");
sb.append("wrap").append("(buffer, 0);\n");
sb.append(indent).append(INDENT).append("encodeWith(encoder, this);\n")
.append(indent).append(INDENT).append("StringBuilder sb = new StringBuilder();\n")
.append(indent).append(INDENT).append("encoder.appendTo(sb);\n")
.append(indent).append(INDENT).append("return sb.toString();\n")
.append(indent).append("}\n");
}
private void generateFields(
final ClassBuilder classBuilder,
final String decoderClassName,
final List tokens,
final String indent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token signalToken = tokens.get(i);
if (signalToken.signal() == Signal.BEGIN_FIELD)
{
final Token encodingToken = tokens.get(i + 1);
final String propertyName = signalToken.name();
switch (encodingToken.signal())
{
case ENCODING:
generatePrimitiveProperty(
classBuilder, decoderClassName, propertyName, signalToken, encodingToken, indent);
break;
case BEGIN_ENUM:
generateEnumProperty(classBuilder, propertyName, signalToken, encodingToken, indent);
break;
case BEGIN_SET:
case BEGIN_COMPOSITE:
generateComplexProperty(classBuilder, propertyName, signalToken, encodingToken, indent);
break;
default:
break;
}
}
}
}
private void generateComplexProperty(
final ClassBuilder classBuilder,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final String typeName = formatDtoClassName(typeToken.applicableTypeName());
final String formattedPropertyName = formatPropertyName(propertyName);
final String fieldName = formatFieldName(propertyName);
classBuilder.appendField()
.append(indent).append("private ").append(typeName).append(" ").append(fieldName)
.append(" = new ").append(typeName).append("();\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public ").append(typeName).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public void ")
.append(formattedPropertyName).append("(").append(typeName).append(" value)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
}
private void generateEnumProperty(
final ClassBuilder classBuilder,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final String enumName = formatClassName(typeToken.applicableTypeName());
final String formattedPropertyName = formatPropertyName(propertyName);
if (fieldToken.isConstantEncoding())
{
final String constValue = fieldToken.encoding().constValue().toString();
final String caseName = constValue.substring(constValue.indexOf(".") + 1);
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public static ").append(enumName).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(enumName).append(".")
.append(caseName).append(";\n")
.append(indent).append("}\n");
}
else
{
final String fieldName = formatFieldName(propertyName);
classBuilder.appendField()
.append(indent).append("private ").append(enumName).append(" ").append(fieldName).append(";\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public ").append(enumName).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public void ").append(formattedPropertyName)
.append("(").append(enumName).append(" value)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
}
}
private void generatePrimitiveProperty(
final ClassBuilder classBuilder,
final String decoderClassName,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
if (typeToken.isConstantEncoding())
{
generateConstPropertyMethods(classBuilder, propertyName, fieldToken, typeToken, indent);
}
else
{
generatePrimitivePropertyMethods(
classBuilder, decoderClassName, propertyName, fieldToken, typeToken, indent);
}
}
private void generatePrimitivePropertyMethods(
final ClassBuilder classBuilder,
final String decoderClassName,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final int arrayLength = typeToken.arrayLength();
if (arrayLength == 1)
{
generateSingleValueProperty(classBuilder, decoderClassName, propertyName, fieldToken, typeToken, indent);
}
else if (arrayLength > 1)
{
generateArrayProperty(classBuilder, decoderClassName, propertyName, fieldToken, typeToken, indent);
}
}
private void generateArrayProperty(
final ClassBuilder classBuilder,
final String decoderClassName,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final String formattedPropertyName = formatPropertyName(propertyName);
final String fieldName = formatFieldName(propertyName);
final String validateMethod = "validate" + toUpperFirstChar(propertyName);
final PrimitiveType primitiveType = typeToken.encoding().primitiveType();
if (primitiveType == PrimitiveType.CHAR)
{
final CharSequence typeName = "String";
classBuilder.appendField()
.append(indent).append("private ").append(typeName).append(" ").append(fieldName).append(";\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public ").append(typeName).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public void ").append(formattedPropertyName)
.append("(").append(typeName).append(" value)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(validateMethod).append("(value);\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
generateArrayValidateMethod(
classBuilder,
decoderClassName,
indent,
validateMethod,
typeName,
".length()",
formattedPropertyName);
}
else
{
final String elementTypeName = javaTypeName(primitiveType);
final String typeName = elementTypeName + "[]";
classBuilder.appendField()
.append(indent).append("private ").append(typeName).append(" ").append(fieldName).append(";\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public ").append(typeName).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public void ").append(formattedPropertyName).append("(")
.append(typeName).append(" value").append(")\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(validateMethod).append("(value);\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
generateArrayValidateMethod(
classBuilder,
decoderClassName,
indent,
validateMethod,
typeName,
".length",
formattedPropertyName);
}
}
private static void generateArrayValidateMethod(
final ClassBuilder classBuilder,
final String decoderClassName,
final String indent,
final String validateMethod,
final CharSequence typeName,
final String lengthAccessor,
final String formattedPropertyName)
{
final StringBuilder validateBuilder = classBuilder.appendPrivate().append("\n")
.append(indent).append("private static void ").append(validateMethod).append("(")
.append(typeName).append(" value)\n")
.append(indent).append("{\n");
validateBuilder.append(indent).append(INDENT)
.append("if (value").append(lengthAccessor).append(" > ").append(decoderClassName).append(".")
.append(formattedPropertyName).append("Length())\n")
.append(indent).append(INDENT)
.append("{\n")
.append(indent).append(INDENT).append(INDENT)
.append("throw new IllegalArgumentException(\"")
.append(formattedPropertyName)
.append(": too many elements: \" + ")
.append("value").append(lengthAccessor).append(");\n")
.append(indent).append(INDENT)
.append("}\n")
.append(indent).append("}\n");
}
private void generateSingleValueProperty(
final ClassBuilder classBuilder,
final String decoderClassName,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
final String typeName = javaTypeName(typeToken.encoding().primitiveType());
final String formattedPropertyName = formatPropertyName(propertyName);
final String fieldName = formatFieldName(propertyName);
final String validateMethod = "validate" + toUpperFirstChar(propertyName);
final boolean representedWithinJavaType = typeToken.encoding().primitiveType() != PrimitiveType.UINT64;
final StringBuilder validationCall = new StringBuilder();
if (representedWithinJavaType)
{
final StringBuilder validateBuilder = classBuilder.appendPrivate().append("\n")
.append(indent).append("private static void ").append(validateMethod).append("(")
.append(typeName).append(" value)\n")
.append(indent).append("{\n");
validateBuilder.append(indent).append(INDENT)
.append("if (value < ")
.append(decoderClassName).append(".").append(formattedPropertyName).append("MinValue() || ")
.append("value").append(" > ")
.append(decoderClassName).append(".").append(formattedPropertyName).append("MaxValue())\n")
.append(indent).append(INDENT)
.append("{\n")
.append(indent).append(INDENT).append(INDENT)
.append("throw new IllegalArgumentException(\"")
.append(propertyName)
.append(": value is out of allowed range: \" + ")
.append("value").append(");\n")
.append(indent).append(INDENT)
.append("}\n")
.append(indent).append("}\n");
validationCall.append(indent).append(INDENT).append(validateMethod).append("(value);\n");
}
classBuilder.appendField()
.append(indent).append("private ").append(typeName).append(" ").append(fieldName).append(";\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public ").append(typeName).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public void ").append(formattedPropertyName).append("(")
.append(typeName).append(" value)\n")
.append(indent).append("{\n")
.append(validationCall)
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
}
private void generateConstPropertyMethods(
final ClassBuilder classBuilder,
final String propertyName,
final Token fieldToken,
final Token typeToken,
final String indent)
{
if (typeToken.encoding().primitiveType() == PrimitiveType.CHAR)
{
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public static String ").append(toLowerFirstChar(propertyName)).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT)
.append("return \"").append(typeToken.encoding().constValue().toString()).append("\";\n")
.append(indent).append("}\n");
}
else
{
final CharSequence literalValue =
generateLiteral(typeToken.encoding().primitiveType(), typeToken.encoding().constValue().toString());
classBuilder.appendPublic().append("\n")
.append(generateDocumentation(indent, fieldToken))
.append(indent).append("public static ")
.append(javaTypeName(typeToken.encoding().primitiveType()))
.append(" ").append(formatPropertyName(propertyName)).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(literalValue).append(";\n")
.append(indent).append("}\n");
}
}
private void generateVarData(
final ClassBuilder classBuilder,
final List tokens,
final String indent)
{
for (int i = 0, size = tokens.size(); i < size; i++)
{
final Token token = tokens.get(i);
if (token.signal() == Signal.BEGIN_VAR_DATA)
{
final String propertyName = token.name();
final Token varDataToken = Generators.findFirst("varData", tokens, i);
final String characterEncoding = varDataToken.encoding().characterEncoding();
final String dtoType = characterEncoding == null ? "byte[]" : "String";
final String fieldName = formatFieldName(propertyName);
final String formattedPropertyName = formatPropertyName(propertyName);
classBuilder.appendField()
.append(indent).append("private ").append(dtoType).append(" ").append(fieldName).append(";\n");
classBuilder.appendPublic().append("\n")
.append(indent).append("public ").append(dtoType).append(" ")
.append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic().append("\n")
.append(indent).append("public void ").append(formattedPropertyName)
.append("(").append(dtoType).append(" value)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append("}\n");
}
}
}
private static String formatDtoClassName(final String name)
{
return formatClassName(name + "Dto");
}
private void generateDtosForTypes() throws IOException
{
if (shouldSupportTypesPackageNames)
{
for (final List tokens : ir.types())
{
final String packageName = tokens.get(0).packageName();
if (packageName != null)
{
packageNameByTypes.add(packageName);
}
}
}
for (final List tokens : ir.types())
{
switch (tokens.get(0).signal())
{
case BEGIN_COMPOSITE:
generateComposite(tokens);
break;
case BEGIN_SET:
generateChoiceSet(tokens);
break;
default:
break;
}
}
}
private void generateComposite(final List tokens) throws IOException
{
final String name = tokens.get(0).applicableTypeName();
final String className = formatDtoClassName(name);
final String encoderClassName = encoderName(name);
final String decoderClassName = decoderName(name);
final String packageName = fetchTypesPackageName(tokens.get(0), ir);
try (Writer out = outputManager.createOutput(className))
{
final List compositeTokens = tokens.subList(1, tokens.size() - 1);
out.append(generateDtoFileHeader(packageName));
out.append("import org.agrona.DirectBuffer;\n");
out.append("import org.agrona.MutableDirectBuffer;\n");
out.append("import org.agrona.concurrent.UnsafeBuffer;\n\n");
out.append(generateDocumentation(BASE_INDENT, tokens.get(0)));
final ClassBuilder classBuilder = new ClassBuilder(className, BASE_INDENT, "public final");
generateCompositePropertyElements(classBuilder, decoderClassName, compositeTokens,
BASE_INDENT + INDENT);
generateCompositeDecodeWith(classBuilder, className, decoderClassName, compositeTokens,
BASE_INDENT + INDENT);
generateCompositeEncodeWith(classBuilder, className, encoderClassName, compositeTokens,
BASE_INDENT + INDENT);
generateDisplay(classBuilder, encoderClassName, encoderClassName + ".ENCODED_LENGTH",
BASE_INDENT + INDENT);
classBuilder.appendTo(out);
}
}
private void generateChoiceSet(final List tokens) throws IOException
{
final String name = tokens.get(0).applicableTypeName();
final String className = formatDtoClassName(name);
final String encoderClassName = encoderName(name);
final String decoderClassName = decoderName(name);
final String packageName = fetchTypesPackageName(tokens.get(0), ir);
try (Writer out = outputManager.createOutput(className))
{
final List setTokens = tokens.subList(1, tokens.size() - 1);
out.append(generateDtoFileHeader(packageName));
out.append(generateDocumentation(BASE_INDENT, tokens.get(0)));
final ClassBuilder classBuilder = new ClassBuilder(className, BASE_INDENT, "public final");
generateChoices(classBuilder, className, setTokens, BASE_INDENT + INDENT);
generateChoiceSetDecodeWith(classBuilder, className, decoderClassName, setTokens, BASE_INDENT + INDENT);
generateChoiceSetEncodeWith(classBuilder, className, encoderClassName, setTokens, BASE_INDENT + INDENT);
classBuilder.appendTo(out);
}
}
private void generateChoiceSetEncodeWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String encoderClassName,
final List setTokens,
final String indent)
{
final StringBuilder encodeBuilder = classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static void encodeWith(\n")
.append(indent).append(INDENT).append(encoderClassName).append(" encoder, ")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n");
encodeBuilder.append(indent).append(INDENT).append("encoder.clear();\n");
for (final Token token : setTokens)
{
if (token.signal() == Signal.CHOICE)
{
final String formattedPropertyName = formatPropertyName(token.name());
encodeBuilder.append(indent).append(INDENT).append("encoder.").append(formattedPropertyName)
.append("(dto.").append(formattedPropertyName).append("());\n");
}
}
encodeBuilder.append(indent).append("}\n");
}
private void generateChoiceSetDecodeWith(
final ClassBuilder classBuilder,
final String dtoClassName,
final String decoderClassName,
final List setTokens,
final String indent)
{
final StringBuilder decodeBuilder = classBuilder.appendPublic()
.append("\n")
.append(indent).append("public static void decodeWith(\n")
.append(indent).append(INDENT).append(decoderClassName).append(" decoder, ")
.append(dtoClassName).append(" dto)\n")
.append(indent).append("{\n");
for (final Token token : setTokens)
{
if (token.signal() == Signal.CHOICE)
{
final String formattedPropertyName = formatPropertyName(token.name());
decodeBuilder.append(indent).append(INDENT).append("dto.").append(formattedPropertyName)
.append("(decoder.").append(formattedPropertyName).append("());\n");
}
}
decodeBuilder.append(indent).append("}\n");
}
private void generateChoices(
final ClassBuilder classBuilder,
final String dtoClassName,
final List setTokens,
final String indent)
{
final List fields = new ArrayList<>();
for (final Token token : setTokens)
{
if (token.signal() == Signal.CHOICE)
{
final String fieldName = formatFieldName(token.name());
final String formattedPropertyName = formatPropertyName(token.name());
fields.add(fieldName);
classBuilder.appendField()
.append(indent).append("boolean ").append(fieldName).append(";\n");
classBuilder.appendPublic()
.append("\n")
.append(indent).append("public boolean ").append(formattedPropertyName).append("()\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append("return ").append(fieldName).append(";\n")
.append(indent).append("}\n");
classBuilder.appendPublic()
.append("\n")
.append(indent).append(dtoClassName).append(" ")
.append(formattedPropertyName).append("(boolean value)\n")
.append(indent).append("{\n")
.append(indent).append(INDENT).append(fieldName).append(" = value;\n")
.append(indent).append(INDENT).append("return this;\n")
.append(indent).append("}\n");
}
}
final StringBuilder clearBuilder = classBuilder.appendPublic()
.append(indent).append(dtoClassName).append(" clear()\n")
.append(indent).append("{\n");
for (final String field : fields)
{
clearBuilder.append(indent).append(INDENT).append(field).append(" = false;\n");
}
clearBuilder.append(indent).append(INDENT).append("return this;\n")
.append(indent).append("}\n");
}
private void generateCompositePropertyElements(
final ClassBuilder classBuilder,
final String decoderClassName,
final List tokens,
final String indent)
{
for (int i = 0; i < tokens.size(); )
{
final Token token = tokens.get(i);
final String propertyName = formatPropertyName(token.name());
switch (token.signal())
{
case ENCODING:
generatePrimitiveProperty(classBuilder, decoderClassName, propertyName, token, token, indent);
break;
case BEGIN_ENUM:
generateEnumProperty(classBuilder, propertyName, token, token, indent);
break;
case BEGIN_SET:
case BEGIN_COMPOSITE:
generateComplexProperty(classBuilder, propertyName, token, token, indent);
break;
default:
break;
}
i += tokens.get(i).componentTokenCount();
}
}
private static StringBuilder generateImportStatements(final Set packages, final String currentPackage)
{
final StringBuilder importStatements = new StringBuilder();
for (final String candidatePackage : packages)
{
if (!candidatePackage.equals(currentPackage))
{
importStatements.append("import ").append(candidatePackage).append(".*;\n");
}
}
if (!importStatements.isEmpty())
{
importStatements.append("\n\n");
}
return importStatements;
}
private CharSequence generateDtoFileHeader(final String packageName)
{
final StringBuilder sb = new StringBuilder();
sb.append("/* Generated SBE (Simple Binary Encoding) message DTO */\n");
sb.append("package ").append(packageName).append(";\n\n");
sb.append(generateImportStatements(packageNameByTypes, packageName));
return sb;
}
private static String generateDocumentation(final String indent, final Token token)
{
final String description = token.description();
if (null == description || description.isEmpty())
{
return "";
}
return
indent + "/**\n" +
indent + " * " + description + "\n" +
indent + " */\n";
}
private static String formatFieldName(final String propertyName)
{
return formatPropertyName(propertyName);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy