| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.plc4x.plugins.codegenerator.protocol.freemarker; |
| |
| import net.objecthunter.exp4j.Expression; |
| import net.objecthunter.exp4j.ExpressionBuilder; |
| import org.apache.plc4x.plugins.codegenerator.types.definitions.*; |
| import org.apache.plc4x.plugins.codegenerator.types.enums.EnumValue; |
| import org.apache.plc4x.plugins.codegenerator.types.fields.*; |
| import org.apache.plc4x.plugins.codegenerator.types.references.ComplexTypeReference; |
| import org.apache.plc4x.plugins.codegenerator.types.references.SimpleTypeReference; |
| import org.apache.plc4x.plugins.codegenerator.types.references.StringTypeReference; |
| import org.apache.plc4x.plugins.codegenerator.types.references.TypeReference; |
| import org.apache.plc4x.plugins.codegenerator.types.terms.*; |
| import org.w3c.dom.Node; |
| |
| import java.util.*; |
| import java.util.stream.Collectors; |
| |
| public abstract class BaseFreemarkerLanguageTemplateHelper implements FreemarkerLanguageTemplateHelper { |
| |
| private final TypeDefinition thisType; |
| private final String protocolName; |
| private final String flavorName; |
| private final Map<String, TypeDefinition> types; |
| |
| // In mspec we are using some virtual virtual fields that are useful for code generation. |
| // As they should be shared over all language template implementations, |
| // these are defined here manually. |
| private static final Map<String, SimpleTypeReference> builtInFields; |
| |
| public static final String CUR_POS = "curPos"; |
| public static final String START_POS = "startPos"; |
| public static final String LAST_ITEM = "lastItem"; |
| public static final String IMPLICIT = "implicit"; |
| |
| static { |
| builtInFields = new HashMap<>(); |
| builtInFields.put(CUR_POS, new SimpleTypeReference() { |
| @Override |
| public SimpleBaseType getBaseType() { |
| return SimpleBaseType.UINT; |
| } |
| |
| @Override |
| public int getSizeInBits() { |
| return 16; |
| } |
| }); |
| builtInFields.put(START_POS, new SimpleTypeReference() { |
| @Override |
| public SimpleBaseType getBaseType() { |
| return SimpleBaseType.UINT; |
| } |
| |
| @Override |
| public int getSizeInBits() { |
| return 16; |
| } |
| }); |
| builtInFields.put(LAST_ITEM, new SimpleTypeReference() { |
| @Override |
| public SimpleBaseType getBaseType() { |
| return SimpleBaseType.BIT; |
| } |
| |
| @Override |
| public int getSizeInBits() { |
| return 1; |
| } |
| }); |
| } |
| |
| protected BaseFreemarkerLanguageTemplateHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types) { |
| this.thisType = thisType; |
| this.protocolName = protocolName; |
| this.flavorName = flavorName; |
| this.types = types; |
| } |
| |
| protected TypeDefinition getThisTypeDefinition() { |
| return thisType; |
| } |
| |
| public String getProtocolName() { |
| return protocolName; |
| } |
| |
| public String getFlavorName() { |
| return flavorName; |
| } |
| |
| public Map<String, TypeDefinition> getTypeDefinitions() { |
| return types; |
| } |
| |
| public List<TypeDefinition> getComplexTypeRootDefinitions() { |
| return types.values().stream().filter(typeDefinition -> (typeDefinition instanceof ComplexTypeDefinition) && |
| !(typeDefinition instanceof DiscriminatedComplexTypeDefinition)).collect(Collectors.toList()); |
| } |
| |
| protected static Map<String, SimpleTypeReference> getBuiltInFieldTypes() { |
| return builtInFields; |
| } |
| |
| /* ********************************************************************************* |
| * Methods that are language-dependent. |
| **********************************************************************************/ |
| |
| public abstract String getLanguageTypeNameForField(Field field); |
| |
| public abstract String getLanguageTypeNameForTypeReference(TypeReference typeReference); |
| |
| public String getReadBufferReadMethodCall(SimpleTypeReference simpleTypeReference) { |
| return getReadBufferReadMethodCall(simpleTypeReference, null, null); |
| } |
| |
| public abstract String getReadBufferReadMethodCall(SimpleTypeReference simpleTypeReference, String valueString, TypedField field); |
| |
| public abstract String getWriteBufferWriteMethodCall(SimpleTypeReference simpleTypeReference, String fieldName, TypedField field); |
| |
| public abstract String getNullValueForTypeReference(TypeReference typeReference); |
| |
| /* ********************************************************************************* |
| * Methods related to type-references. |
| **********************************************************************************/ |
| |
| /** |
| * @param typeReference type reference |
| * @return true if the given type reference is a simple type reference. |
| */ |
| public boolean isSimpleTypeReference(TypeReference typeReference) { |
| return typeReference instanceof SimpleTypeReference; |
| } |
| |
| /** |
| * @param typeReference type reference |
| * @return true if the given type reference is a byte based type reference. |
| */ |
| public boolean isByteBased(TypeReference typeReference) { |
| if (!isSimpleTypeReference(typeReference)) { |
| return false; |
| } |
| return ((SimpleTypeReference) typeReference).getBaseType() == SimpleTypeReference.SimpleBaseType.BYTE; |
| } |
| |
| /** |
| * @param typeReference type reference |
| * @return true if the given type reference is a complex type reference. |
| */ |
| public boolean isComplexTypeReference(TypeReference typeReference) { |
| return typeReference instanceof ComplexTypeReference; |
| } |
| |
| public boolean isEnumTypeReference(TypeReference typeReference) { |
| if (!isComplexTypeReference(typeReference)) { |
| return false; |
| } |
| return getTypeDefinitionForTypeReference(typeReference) instanceof EnumTypeDefinition; |
| } |
| |
| public boolean isStringTypeReference(TypeReference typeReference) { |
| return typeReference instanceof StringTypeReference; |
| } |
| |
| /** |
| * Helper for collecting referenced complex types as these usually ned to be |
| * imported in some way. |
| * |
| * @return Collection of all complex type references used in fields or enum constants. |
| */ |
| public Collection<String> getComplexTypeReferences() { |
| return getComplexTypeReferences(thisType); |
| } |
| |
| /** |
| * Helper for collecting referenced complex types as these usually need to be |
| * imported in some way. |
| * |
| * @param baseType the base type we want to get the type references from |
| * @return collection of complex type references used in the type. |
| */ |
| public Collection<String> getComplexTypeReferences(TypeDefinition baseType) { |
| Set<String> complexTypeReferences = new HashSet<>(); |
| // If this is a subtype of a discriminated type, we have to add a reference to the parent type. |
| if (baseType instanceof DiscriminatedComplexTypeDefinition) { |
| DiscriminatedComplexTypeDefinition discriminatedComplexTypeDefinition = (DiscriminatedComplexTypeDefinition) baseType; |
| if (!discriminatedComplexTypeDefinition.isAbstract()) { |
| complexTypeReferences.add(((ComplexTypeReference) |
| discriminatedComplexTypeDefinition.getParentType().getTypeReference()).getName()); |
| } |
| } |
| // If it's a complex type definition, add all the types referenced by any property fields |
| // (Includes any types referenced by sub-types in case this is a discriminated type parent) |
| if (baseType instanceof ComplexTypeDefinition) { |
| ComplexTypeDefinition complexTypeDefinition = (ComplexTypeDefinition) baseType; |
| for (Field field : complexTypeDefinition.getFields()) { |
| if (field instanceof PropertyField) { |
| PropertyField propertyField = (PropertyField) field; |
| if (propertyField.getType() instanceof ComplexTypeReference) { |
| ComplexTypeReference complexTypeReference = (ComplexTypeReference) propertyField.getType(); |
| complexTypeReferences.add(complexTypeReference.getName()); |
| } |
| } else if (field instanceof SwitchField) { |
| SwitchField switchField = (SwitchField) field; |
| for (DiscriminatedComplexTypeDefinition switchCase : switchField.getCases()) { |
| complexTypeReferences.addAll(getComplexTypeReferences(switchCase)); |
| } |
| } |
| } |
| } |
| // In case this is a enum type, we have to check all the constant types. |
| else if (baseType instanceof EnumTypeDefinition) { |
| for (String constantName : ((EnumTypeDefinition) baseType).getConstantNames()) { |
| final TypeReference constantType = ((EnumTypeDefinition) thisType).getConstantType(constantName); |
| if (constantType instanceof ComplexTypeReference) { |
| ComplexTypeReference complexTypeReference = (ComplexTypeReference) constantType; |
| complexTypeReferences.add(complexTypeReference.getName()); |
| } |
| } |
| } |
| // If the type has any parser arguments, these have to be checked too. |
| if (baseType.getParserArguments() != null) { |
| for (Argument parserArgument : baseType.getParserArguments()) { |
| if (parserArgument.getType() instanceof ComplexTypeReference) { |
| ComplexTypeReference complexTypeReference = (ComplexTypeReference) parserArgument.getType(); |
| complexTypeReferences.add(complexTypeReference.getName()); |
| } |
| } |
| } |
| return complexTypeReferences; |
| } |
| |
| /** |
| * Little helper to return the type of a given property. |
| * |
| * @param baseType base type definition that contains the given property. |
| * @param propertyName name of the property |
| * @return the type reference of the given property |
| */ |
| public Optional<TypeReference> getTypeReferenceForProperty(ComplexTypeDefinition baseType, String propertyName) { |
| // If this is a built-in type, use that. |
| if (builtInFields.containsKey(propertyName)) { |
| return Optional.of(builtInFields.get(propertyName)); |
| } |
| // Check if the expression root is referencing a field |
| final Optional<PropertyField> propertyFieldOptional = baseType.getPropertyFields().stream() |
| .filter(propertyField -> propertyField.getName().equals(propertyName)) |
| .findFirst(); |
| if (propertyFieldOptional.isPresent()) { |
| final PropertyField propertyField = propertyFieldOptional.get(); |
| return Optional.of(propertyField.getType()); |
| } |
| // Check if the expression is a ImplicitField |
| final Optional<ImplicitField> implicitFieldOptional = baseType.getFields().stream() |
| .filter(ImplicitField.class::isInstance) |
| .map(ImplicitField.class::cast) |
| .filter(implicitField -> implicitField.getName().equals(propertyName)) |
| .findFirst(); |
| if (implicitFieldOptional.isPresent()) { |
| final ImplicitField implicitField = implicitFieldOptional.get(); |
| return Optional.of(implicitField.getType()); |
| } |
| // Check if the expression is a VirtualField |
| final Optional<VirtualField> virtualFieldOptional = baseType.getFields().stream() |
| .filter(VirtualField.class::isInstance) |
| .map(VirtualField.class::cast) |
| .filter(virtualField -> virtualField.getName().equals(propertyName)) |
| .findFirst(); |
| if (virtualFieldOptional.isPresent()) { |
| final VirtualField virtualField = virtualFieldOptional.get(); |
| return Optional.of(virtualField.getType()); |
| } |
| // Check if the expression root is referencing an argument |
| if (baseType.getParserArguments() != null) { |
| final Optional<Argument> argumentOptional = Arrays.stream(baseType.getParserArguments()) |
| .filter(argument -> argument.getName().equals(propertyName)) |
| .findFirst(); |
| if (argumentOptional.isPresent()) { |
| final Argument argument = argumentOptional.get(); |
| return Optional.of(argument.getType()); |
| } |
| } |
| // Check if the expression is a DiscriminatorField |
| // This is a more theoretical case where the expression is referencing a discriminator value of the current type |
| final Optional<DiscriminatorField> discriminatorFieldOptional = baseType.getFields().stream() |
| .filter(DiscriminatorField.class::isInstance) |
| .map(DiscriminatorField.class::cast) |
| .filter(discriminatorField -> discriminatorField.getName().equals(propertyName)) |
| .findFirst(); |
| if (discriminatorFieldOptional.isPresent()) { |
| final DiscriminatorField discriminatorField = discriminatorFieldOptional.get(); |
| return Optional.of(discriminatorField.getType()); |
| } |
| return Optional.empty(); |
| } |
| |
| /** |
| * Enums are always based on a main type. This helper accesses this information in a safe manner. |
| * |
| * @param typeReference type reference |
| * @return simple type reference for the enum type referenced by the given type reference |
| */ |
| public SimpleTypeReference getEnumBaseTypeReference(TypeReference typeReference) { |
| if (!(typeReference instanceof ComplexTypeReference)) { |
| throw new FreemarkerException("type reference for enum types must be of type complex type"); |
| } |
| ComplexTypeReference complexTypeReference = (ComplexTypeReference) typeReference; |
| final TypeDefinition typeDefinition = types.get(complexTypeReference.getName()); |
| if (typeDefinition == null) { |
| throw new FreemarkerException("Couldn't find given enum type definition with name " + complexTypeReference.getName()); |
| } |
| if (!(typeDefinition instanceof EnumTypeDefinition)) { |
| throw new FreemarkerException("Referenced type with name " + complexTypeReference.getName() + " is not an enum type"); |
| } |
| EnumTypeDefinition enumTypeDefinition = (EnumTypeDefinition) typeDefinition; |
| // Enum types always have simple type references. |
| return (SimpleTypeReference) enumTypeDefinition.getType(); |
| } |
| |
| /* ********************************************************************************* |
| * Methods related to fields. |
| **********************************************************************************/ |
| |
| public boolean hasFieldOfType(String fieldTypeName) { |
| if (getThisTypeDefinition() instanceof ComplexTypeDefinition) { |
| return ((ComplexTypeDefinition) getThisTypeDefinition()).getFields().stream().anyMatch(field -> field.getTypeName().equals(fieldTypeName)); |
| } |
| return false; |
| } |
| |
| public boolean hasFieldsWithNames(List<Field> fields, String... names) { |
| for (String name : names) { |
| boolean foundName = false; |
| for (Field field : fields) { |
| if (field instanceof NamedField && name.equals(((NamedField) field).getName())) { |
| foundName = true; |
| break; |
| } |
| } |
| if (!foundName) { |
| return false; |
| } |
| } |
| // TODO: document why true is returned here. |
| return true; |
| } |
| |
| public Field getFieldForNameFromCurrentOrParent(String fieldName) { |
| if (!(getThisTypeDefinition() instanceof ComplexTypeDefinition)) { |
| return null; |
| } |
| return ((ComplexTypeDefinition) getThisTypeDefinition()).getAllPropertyFields() |
| .stream() |
| .filter(propertyField -> propertyField.getName().equals(fieldName)) |
| .findFirst() |
| .orElse(null); |
| } |
| |
| public Field getFieldForNameFromCurrent(String fieldName) { |
| if (!(getThisTypeDefinition() instanceof ComplexTypeDefinition)) { |
| return null; |
| } |
| return ((ComplexTypeDefinition) getThisTypeDefinition()).getPropertyFields() |
| .stream() |
| .filter(propertyField -> propertyField.getName().equals(fieldName)) |
| .findFirst() |
| .orElse(null); |
| } |
| |
| public boolean isAbstractField(Field field) { |
| return field instanceof AbstractField; |
| } |
| |
| public boolean isArrayField(Field field) { |
| return field instanceof ArrayField; |
| } |
| |
| public boolean isChecksumField(Field field) { |
| return field instanceof ChecksumField; |
| } |
| |
| public boolean isConstField(Field field) { |
| return field instanceof ConstField; |
| } |
| |
| public boolean isDiscriminatorField(Field field) { |
| return field instanceof DiscriminatorField; |
| } |
| |
| public boolean isEnumField(Field field) { |
| if (!(field instanceof TypedField)) { |
| return false; |
| } |
| TypedField typedField = (TypedField) field; |
| TypeReference typeReference = typedField.getType(); |
| if (isSimpleTypeReference(typeReference)) { |
| return false; |
| } |
| TypeDefinition typeDefinition = getTypeDefinitionForTypeReference(typedField.getType()); |
| return typeDefinition instanceof EnumTypeDefinition; |
| } |
| |
| public boolean isImplicitField(Field field) { |
| return field instanceof ImplicitField; |
| } |
| |
| public boolean isManualArrayField(Field field) { |
| return field instanceof ManualArrayField; |
| } |
| |
| public boolean isNamedField(Field field) { |
| return field instanceof NamedField; |
| } |
| |
| public boolean isOptionalField(Field field) { |
| return field instanceof OptionalField; |
| } |
| |
| public boolean isPaddingField(Field field) { |
| return field instanceof PaddingField; |
| } |
| |
| public boolean isPropertyField(Field field) { |
| return field instanceof PropertyField; |
| } |
| |
| public boolean isReservedField(Field field) { |
| return field instanceof ReservedField; |
| } |
| |
| public boolean isSimpleField(Field field) { |
| return field instanceof SimpleField; |
| } |
| |
| public boolean isSwitchField(Field field) { |
| return field instanceof SwitchField; |
| } |
| |
| public boolean isTypedField(Field field) { |
| return field instanceof TypedField; |
| } |
| |
| public boolean isVirtualField(Field field) { |
| return field instanceof VirtualField; |
| } |
| |
| public boolean isCountArrayField(Field field) { |
| if (field instanceof ArrayField) { |
| ArrayField arrayField = (ArrayField) field; |
| return arrayField.getLoopType() == ArrayField.LoopType.COUNT; |
| } |
| if (field instanceof ManualArrayField) { |
| ManualArrayField arrayField = (ManualArrayField) field; |
| return arrayField.getLoopType() == ManualArrayField.LoopType.COUNT; |
| } |
| return false; |
| } |
| |
| public boolean isLengthArrayField(Field field) { |
| if (field instanceof ArrayField) { |
| ArrayField arrayField = (ArrayField) field; |
| return arrayField.getLoopType() == ArrayField.LoopType.LENGTH; |
| } |
| if (field instanceof ManualArrayField) { |
| ManualArrayField arrayField = (ManualArrayField) field; |
| return arrayField.getLoopType() == ManualArrayField.LoopType.LENGTH; |
| } |
| return false; |
| } |
| |
| public boolean isTerminatedArrayField(Field field) { |
| if (field instanceof ArrayField) { |
| ArrayField arrayField = (ArrayField) field; |
| return arrayField.getLoopType() == ArrayField.LoopType.TERMINATED; |
| } |
| if (field instanceof ManualArrayField) { |
| ManualArrayField arrayField = (ManualArrayField) field; |
| return arrayField.getLoopType() == ManualArrayField.LoopType.TERMINATED; |
| } |
| return false; |
| } |
| |
| /** |
| * @return switch field of the current base type. |
| */ |
| public SwitchField getSwitchField() { |
| return getSwitchField(thisType); |
| } |
| |
| /** |
| * @return switch field of the provided base type. |
| */ |
| protected SwitchField getSwitchField(TypeDefinition typeDefinition) { |
| if (!(typeDefinition instanceof ComplexTypeDefinition)) { |
| return null; |
| } |
| ComplexTypeDefinition complexTypeDefinition = (ComplexTypeDefinition) typeDefinition; |
| // Sebastian would be proud of me ;-) |
| return (SwitchField) complexTypeDefinition.getFields().stream() |
| .filter(SwitchField.class::isInstance) |
| .findFirst() |
| .orElse(null); |
| } |
| |
| public Collection<Field> getPropertyAndSwitchFields() { |
| return getPropertyAndSwitchFields(thisType); |
| } |
| |
| public Collection<Field> getPropertyAndSwitchFields(TypeDefinition typeDefinition) { |
| if (!(thisType instanceof ComplexTypeDefinition)) { |
| return Collections.emptyList(); |
| } |
| return ((ComplexTypeDefinition) thisType).getFields().stream() |
| .filter(field -> (field instanceof PropertyField) || (field instanceof SwitchField)) |
| .collect(Collectors.toList()); |
| } |
| |
| /* ********************************************************************************* |
| * Methods related to type-definitions. |
| **********************************************************************************/ |
| |
| public boolean isDiscriminatedParentTypeDefinition() { |
| return isDiscriminatedParentTypeDefinition(thisType); |
| } |
| |
| public boolean isDiscriminatedParentTypeDefinition(TypeDefinition typeDefinition) { |
| return (typeDefinition instanceof ComplexTypeDefinition) && ((ComplexTypeDefinition) typeDefinition).isAbstract(); |
| } |
| |
| public boolean isDiscriminatedChildTypeDefinition() { |
| return isDiscriminatedChildTypeDefinition(thisType); |
| } |
| |
| public boolean isDiscriminatedChildTypeDefinition(TypeDefinition typeDefinition) { |
| return (typeDefinition instanceof DiscriminatedComplexTypeDefinition) && !((ComplexTypeDefinition) typeDefinition).isAbstract(); |
| } |
| |
| public TypeDefinition getTypeDefinitionForTypeReference(TypeReference typeReference) { |
| if (!isComplexTypeReference(typeReference)) { |
| throw new FreemarkerException("Type reference must be a complex type reference"); |
| } |
| return getTypeDefinitions().get(((ComplexTypeReference) typeReference).getName()); |
| } |
| |
| /** |
| * @return list of sub-types for the current base type or an empty collection, if there are none |
| */ |
| public List<DiscriminatedComplexTypeDefinition> getSubTypeDefinitions() { |
| return getSubTypeDefinitions(thisType); |
| } |
| |
| /** |
| * @return list of sub-types for a given type definition or an empty collection, if there are none |
| */ |
| public List<DiscriminatedComplexTypeDefinition> getSubTypeDefinitions(TypeDefinition type) { |
| SwitchField switchField = getSwitchField(type); |
| if (switchField != null) { |
| return switchField.getCases(); |
| } |
| return Collections.emptyList(); |
| } |
| |
| /* ********************************************************************************* |
| * Methods related to terms and expressions. |
| **********************************************************************************/ |
| |
| /** |
| * Check if the expression doesn't reference any variables. |
| * If this is the case, the expression can be evaluated at code-generation time. |
| * |
| * @param term term |
| * @return true if it doesn't reference any variable literals. |
| */ |
| protected boolean isFixedValueExpression(Term term) { |
| if (term instanceof VariableLiteral) { |
| return false; |
| } |
| if (term instanceof UnaryTerm) { |
| UnaryTerm unaryTerm = (UnaryTerm) term; |
| return isFixedValueExpression(unaryTerm.getA()); |
| } |
| if (term instanceof BinaryTerm) { |
| BinaryTerm binaryTerm = (BinaryTerm) term; |
| return isFixedValueExpression(binaryTerm.getA()) && isFixedValueExpression(binaryTerm.getB()); |
| } |
| if (term instanceof TernaryTerm) { |
| TernaryTerm ternaryTerm = (TernaryTerm) term; |
| return isFixedValueExpression(ternaryTerm.getA()) && isFixedValueExpression(ternaryTerm.getB()) && |
| isFixedValueExpression(ternaryTerm.getC()); |
| } |
| return true; |
| } |
| |
| protected int evaluateFixedValueExpression(Term term) { |
| final Expression expression = new ExpressionBuilder(toString(term)).build(); |
| return (int) expression.evaluate(); |
| } |
| |
| protected String toString(Term term) { |
| if (term instanceof NullLiteral) { |
| return "null"; |
| } |
| if (term instanceof BooleanLiteral) { |
| return Boolean.toString(((BooleanLiteral) term).getValue()); |
| } |
| if (term instanceof NumericLiteral) { |
| return ((NumericLiteral) term).getNumber().toString(); |
| } |
| if (term instanceof StringLiteral) { |
| return "\"" + ((StringLiteral) term).getValue() + "\""; |
| } |
| if (term instanceof UnaryTerm) { |
| return ((UnaryTerm) term).getOperation() + toString(((UnaryTerm) term).getA()); |
| } |
| if (term instanceof BinaryTerm) { |
| return toString(((BinaryTerm) term).getA()) + ((BinaryTerm) term).getOperation() + toString(((BinaryTerm) term).getB()); |
| } |
| if (term instanceof TernaryTerm) { |
| return "(" + toString(((TernaryTerm) term).getA()) + ") ? (" + toString(((TernaryTerm) term).getB()) + |
| ") : (" + toString(((TernaryTerm) term).getC()) + ")"; |
| } |
| return ""; |
| } |
| |
| /* ********************************************************************************* |
| * Methods related to discriminators. |
| **********************************************************************************/ |
| |
| private Optional<TypeReference> getDiscriminatorType(ComplexTypeDefinition parentType, Term disciminatorExpression) { |
| if (!(disciminatorExpression instanceof VariableLiteral)) { |
| throw new FreemarkerException("Currently no arithmetic expressions are supported as discriminator expressions."); |
| } |
| VariableLiteral variableLiteral = (VariableLiteral) disciminatorExpression; |
| Optional<TypeReference> type = getTypeReferenceForProperty(parentType, variableLiteral.getName()); |
| // If we found something but there's a "rest" left, we got to use the type we |
| // found in this level, get that type's definition and continue from there. |
| if (type.isPresent() && (variableLiteral.getChild() != null)) { |
| TypeReference typeReference = type.get(); |
| if (typeReference instanceof ComplexTypeReference) { |
| ComplexTypeReference complexTypeReference = (ComplexTypeReference) typeReference; |
| final TypeDefinition typeDefinition = this.types.get(complexTypeReference.getName()); |
| if (typeDefinition instanceof ComplexTypeDefinition) { |
| return getDiscriminatorType((ComplexTypeDefinition) typeDefinition, variableLiteral.getChild()); |
| } |
| } |
| } |
| return type; |
| } |
| |
| /** |
| * Get an ordered list of generated names for the discriminators. |
| * These names can be used to access the type definitions as well as well as the values. |
| * |
| * @return list of symbolic names for the discriminators. |
| */ |
| public List<String> getDiscriminatorNames() { |
| TypeDefinition baseType = thisType; |
| if (thisType.getParentType() != null) { |
| baseType = thisType.getParentType(); |
| } |
| final SwitchField switchField = getSwitchField(baseType); |
| List<String> discriminatorNames = new ArrayList<>(); |
| if (switchField != null) { |
| for (Term discriminatorExpression : switchField.getDiscriminatorExpressions()) { |
| discriminatorNames.add(getDiscriminatorName(discriminatorExpression)); |
| } |
| } |
| return discriminatorNames; |
| } |
| |
| /** |
| * Check if there's any field with the given name. |
| * This is required to suppress the generation of a discriminator field |
| * in case a named field is providing the information. |
| * |
| * @param discriminatorName name of the discriminator name |
| * @return true if a field with the given name already exists in the same type. |
| */ |
| public boolean isNonDiscriminatorField(String discriminatorName) { |
| return ((ComplexTypeDefinition) thisType).getAllPropertyFields().stream().anyMatch( |
| field -> !(field instanceof DiscriminatorField) && field.getName().equals(discriminatorName)); |
| } |
| |
| /** |
| * Check if there's any field with the given name. |
| * This is required to suppress the generation of a virtual field |
| * in case a discriminated field is providing the information. |
| * |
| * @param discriminatorName name of the virtual name |
| * @return true if a field with the given name already exists in the same type. |
| */ |
| public boolean isDiscriminatorField(String discriminatorName) { |
| List<String> names = getDiscriminatorNames(); |
| if (names != null) { |
| return getDiscriminatorNames().stream().anyMatch( |
| field -> field.equals(discriminatorName)); |
| } |
| return false; |
| } |
| |
| /** |
| * Converts a given discriminator description into a symbolic name. |
| * |
| * @param discriminatorExpression discriminator expression |
| * @return name |
| */ |
| public String getDiscriminatorName(Term discriminatorExpression) { |
| if (discriminatorExpression instanceof Literal) { |
| Literal literal = (Literal) discriminatorExpression; |
| if (literal instanceof NullLiteral) { |
| return "null"; |
| } else if (literal instanceof BooleanLiteral) { |
| return Boolean.toString(((BooleanLiteral) literal).getValue()); |
| } else if (literal instanceof NumericLiteral) { |
| return ((NumericLiteral) literal).getNumber().toString(); |
| } else if (literal instanceof StringLiteral) { |
| return ((StringLiteral) literal).getValue(); |
| } else if (literal instanceof VariableLiteral) { |
| VariableLiteral variableLiteral = (VariableLiteral) literal; |
| return getVariableLiteralName(variableLiteral); |
| } |
| } else if (discriminatorExpression instanceof UnaryTerm) { |
| UnaryTerm unaryTerm = (UnaryTerm) discriminatorExpression; |
| return getDiscriminatorName(unaryTerm.getA()); |
| } else if (discriminatorExpression instanceof BinaryTerm) { |
| BinaryTerm binaryTerm = (BinaryTerm) discriminatorExpression; |
| return getDiscriminatorName(binaryTerm.getA()) + "_" + getDiscriminatorName(binaryTerm.getB()); |
| } else if (discriminatorExpression instanceof TernaryTerm) { |
| TernaryTerm ternaryTerm = (TernaryTerm) discriminatorExpression; |
| return getDiscriminatorName(ternaryTerm.getA()) + "_" + getDiscriminatorName(ternaryTerm.getB()) |
| + "_" + getDiscriminatorName(ternaryTerm.getC()); |
| } |
| return ""; |
| } |
| |
| private String getVariableLiteralName(VariableLiteral variableLiteral) { |
| String rest = ""; |
| if (variableLiteral.getChild() != null) { |
| rest = getVariableLiteralName(variableLiteral.getChild()); |
| rest = rest.substring(0, 1).toUpperCase() + rest.substring(1); |
| } |
| return variableLiteral.getName() + rest; |
| } |
| |
| /** |
| * Get a list of the types for every discriminator name. |
| * |
| * @return Map mapping discriminator names to types. |
| */ |
| public Map<String, TypeReference> getDiscriminatorTypes() { |
| // Get the parent type (Which contains the typeSwitch field) |
| ComplexTypeDefinition parentType; |
| if (thisType instanceof DiscriminatedComplexTypeDefinition) { |
| parentType = (ComplexTypeDefinition) thisType.getParentType(); |
| } else { |
| parentType = (ComplexTypeDefinition) thisType; |
| } |
| // Get the typeSwitch field from that. |
| final SwitchField switchField = getSwitchField(parentType); |
| if (switchField == null) { |
| return Collections.emptyMap(); |
| } |
| Map<String, TypeReference> discriminatorTypes = new TreeMap<>(); |
| for (Term discriminatorExpression : switchField.getDiscriminatorExpressions()) { |
| // Get some symbolic name we can use. |
| String discriminatorName = getDiscriminatorName(discriminatorExpression); |
| Optional<TypeReference> discriminatorType = getDiscriminatorType(parentType, discriminatorExpression); |
| discriminatorTypes.put(discriminatorName, discriminatorType.orElse(null)); |
| } |
| return discriminatorTypes; |
| } |
| |
| public Map<String, String> getDiscriminatorValues(TypeDefinition type) { |
| if (!(type instanceof DiscriminatedComplexTypeDefinition)) { |
| return Collections.emptyMap(); |
| } |
| DiscriminatedComplexTypeDefinition switchType = (DiscriminatedComplexTypeDefinition) type; |
| final List<String> discriminatorNames = getDiscriminatorNames(); |
| final Map<String, String> discriminatorValues = new LinkedHashMap<>(); |
| for (int i = 0; i < discriminatorNames.size(); i++) { |
| String discriminatorValue; |
| if (i < switchType.getDiscriminatorValues().length) { |
| discriminatorValue = switchType.getDiscriminatorValues()[i]; |
| } else { |
| discriminatorValue = null; |
| } |
| discriminatorValues.put(discriminatorNames.get(i), discriminatorValue); |
| } |
| return discriminatorValues; |
| } |
| |
| /** |
| * Get a list of the values for every discriminator name for every discriminated type. |
| * |
| * @return Map mapping discriminator names to discriminator values for every discriminated type. |
| */ |
| public Map<String, Map<String, String>> getDiscriminatorValues() { |
| // Get the parent type (Which contains the typeSwitch field) |
| ComplexTypeDefinition parentType; |
| if (thisType instanceof DiscriminatedComplexTypeDefinition) { |
| parentType = (ComplexTypeDefinition) thisType.getParentType(); |
| } else { |
| parentType = (ComplexTypeDefinition) thisType; |
| } |
| // Get the typeSwitch field from that. |
| final SwitchField switchField = getSwitchField(parentType); |
| if (switchField == null) { |
| return Collections.emptyMap(); |
| } |
| // Build a map containing the named discriminator values for every case of the typeSwitch. |
| Map<String, Map<String, String>> discriminatorTypes = new LinkedHashMap<>(); |
| for (DiscriminatedComplexTypeDefinition switchCase : switchField.getCases()) { |
| discriminatorTypes.put(switchCase.getName(), getDiscriminatorValues(switchCase)); |
| } |
| return discriminatorTypes; |
| } |
| |
| public TypeReference getArgumentType(TypeReference typeReference, int index) { |
| if (!(typeReference instanceof ComplexTypeReference)) { |
| throw new FreemarkerException("Only complex type references supported here."); |
| } |
| ComplexTypeReference complexTypeReference = (ComplexTypeReference) typeReference; |
| if (!getTypeDefinitions().containsKey(complexTypeReference.getName())) { |
| throw new FreemarkerException("Could not find definition of complex type " + complexTypeReference.getName()); |
| } |
| TypeDefinition complexTypeDefinition = getTypeDefinitions().get(complexTypeReference.getName()); |
| if (complexTypeDefinition.getParserArguments().length <= index) { |
| throw new FreemarkerException("Type " + complexTypeReference.getName() + " specifies too few parser arguments"); |
| } |
| return complexTypeDefinition.getParserArguments()[index].getType(); |
| } |
| |
| /** |
| * Filters out the arguments that are user for serializiation. |
| * |
| * @param arguments list of all arguments. |
| * @return list of arguments that are used during serialization. |
| */ |
| public List<Argument> getSerializerArguments(Argument[] arguments) { |
| if (arguments == null) { |
| return Collections.emptyList(); |
| } |
| List<Argument> serializerArguments = new LinkedList<>(); |
| for (Argument argument : arguments) { |
| if (LAST_ITEM.equals(argument.getName())) { |
| serializerArguments.add(argument); |
| } |
| } |
| return serializerArguments; |
| } |
| |
| public List<Term> getSerializerTerms(Term[] terms) { |
| if (terms == null) { |
| return Collections.emptyList(); |
| } |
| List<Term> serializerTerms = new LinkedList<>(); |
| for (Term term : terms) { |
| if (term.contains(LAST_ITEM)) { |
| serializerTerms.add(term); |
| } |
| } |
| return serializerTerms; |
| } |
| |
| public boolean hasLastItemTerm(Term[] terms) { |
| if (terms == null) { |
| return false; |
| } |
| for (Term term : terms) { |
| if (term.contains(LAST_ITEM)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| public boolean discriminatorValueNeedsStringEqualityCheck(Term term) { |
| if (!(term instanceof VariableLiteral)) { |
| return false; |
| } |
| |
| VariableLiteral variableLiteral = (VariableLiteral) term; |
| // If this literal references an Enum type, then we have to output it differently. |
| if (getTypeDefinitions().get(variableLiteral.getName()) instanceof EnumTypeDefinition) { |
| return false; |
| } |
| |
| if (getThisTypeDefinition() instanceof ComplexTypeDefinition) { |
| Field referencedField = ((ComplexTypeDefinition) getThisTypeDefinition()).getFields().stream().filter(field -> ((field instanceof NamedField) && ((NamedField) field).getName().equals(variableLiteral.getName()))).findFirst().orElse(null); |
| if (referencedField instanceof TypedField |
| && ((TypedField) referencedField).getType() instanceof StringTypeReference) { |
| return true; |
| } |
| } |
| if (getThisTypeDefinition().getParserArguments() != null) { |
| for (Argument parserArgument : getThisTypeDefinition().getParserArguments()) { |
| if (parserArgument.getName().equals(variableLiteral.getName()) |
| && parserArgument.getType() instanceof StringTypeReference) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public boolean isEnumExpression(String expression) { |
| if (!expression.contains(".")) { |
| return false; |
| } |
| String enumName = expression.substring(0, expression.indexOf('.')); |
| TypeDefinition typeDefinition = this.getTypeDefinitions().get(enumName); |
| return (typeDefinition instanceof EnumTypeDefinition); |
| } |
| |
| public Collection<EnumValue> getUniqueEnumValues(EnumValue[] enumValues) { |
| Map<String, EnumValue> filteredEnumValues = new TreeMap<>(); |
| for (EnumValue enumValue : enumValues) { |
| if (!filteredEnumValues.containsKey(enumValue.getValue())) { |
| filteredEnumValues.put(enumValue.getValue(), enumValue); |
| } |
| } |
| return filteredEnumValues.values(); |
| } |
| |
| public Collection<EnumValue> getEnumValuesForUniqueConstantValues(EnumValue[] enumValues, String constantName) { |
| Map<String, EnumValue> filteredEnumValues = new TreeMap<>(); |
| for (EnumValue enumValue : enumValues) { |
| if (!filteredEnumValues.containsKey(enumValue.getConstant(constantName))) { |
| filteredEnumValues.put(enumValue.getConstant(constantName), enumValue); |
| } |
| } |
| return filteredEnumValues.values(); |
| } |
| |
| public Collection<EnumValue> getEnumValuesForConstantValue(EnumValue[] enumValues, String constantName, String constantValue) { |
| List<EnumValue> filteredEnumValues = new ArrayList<>(); |
| for (EnumValue enumValue : enumValues) { |
| if (enumValue.getConstant(constantName).equals(constantValue)) { |
| filteredEnumValues.add(enumValue); |
| } |
| } |
| return filteredEnumValues; |
| } |
| |
| public SimpleTypeReference getEnumFieldSimpleTypeReference(TypeReference type, String fieldName) { |
| TypeDefinition typeDefinition = getTypeDefinitionForTypeReference(type); |
| |
| if (typeDefinition instanceof EnumTypeDefinition |
| && ((EnumTypeDefinition) typeDefinition).getConstantType(fieldName) instanceof SimpleTypeReference) { |
| return (SimpleTypeReference) ((EnumTypeDefinition) typeDefinition).getConstantType(fieldName); |
| } |
| return null; |
| } |
| |
| /** |
| * Confirms if a variable is an implicit variable. These need to be handled differently when serializing and parsing. |
| * |
| * @param vl The variable to search for. |
| * @return boolean returns true if the variable's name is an implicit field |
| */ |
| protected boolean isVariableLiteralImplicitField(VariableLiteral vl) { |
| List<Field> fields = null; |
| if (thisType instanceof ComplexTypeDefinition) { |
| ComplexTypeDefinition complexType = (ComplexTypeDefinition) getThisTypeDefinition(); |
| fields = complexType.getFields(); |
| } |
| if (fields == null) { |
| return false; |
| } |
| for (Field field : fields) { |
| if (field.getTypeName().equals(IMPLICIT)) { |
| ImplicitField implicitField = (ImplicitField) field; |
| if (vl.getName().equals(implicitField.getName())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the implicit field that has the same name as the variable. These need to be handled differently when serializing and parsing. |
| * |
| * @param vl The variable to search for. |
| * @return ImplicitField returns the implicit field that corresponds to the variable's name. |
| */ |
| protected ImplicitField getReferencedImplicitField(VariableLiteral vl) { |
| List<Field> fields = null; |
| if (thisType instanceof ComplexTypeDefinition) { |
| ComplexTypeDefinition complexType = (ComplexTypeDefinition) getThisTypeDefinition(); |
| fields = complexType.getFields(); |
| } |
| if (fields == null) { |
| return null; |
| } |
| for (Field field : fields) { |
| if (field.getTypeName().equals(IMPLICIT)) { |
| ImplicitField implicitField = (ImplicitField) field; |
| if (vl.getName().equals(implicitField.getName())) { |
| return implicitField; |
| } |
| } |
| } |
| return null; |
| } |
| |
| } |