/*
* 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
*
* 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 org.apache.plc4x.plugins.codegenerator.protocol.freemarker;
import net.objecthunter.exp4j.Expression;
import net.objecthunter.exp4j.ExpressionBuilder;
import org.apache.plc4x.plugins.codegenerator.language.mspec.model.definitions.DefaultDataIoTypeDefinition;
import org.apache.plc4x.plugins.codegenerator.language.mspec.model.references.DefaultBooleanTypeReference;
import org.apache.plc4x.plugins.codegenerator.language.mspec.model.references.DefaultIntegerTypeReference;
import org.apache.plc4x.plugins.codegenerator.language.mspec.model.references.DefaultUndefinedTypeReference;
import org.apache.plc4x.plugins.codegenerator.language.mspec.model.terms.WildcardTerm;
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.*;
import org.apache.plc4x.plugins.codegenerator.types.terms.Term;
import org.apache.plc4x.plugins.codegenerator.types.terms.VariableLiteral;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public abstract class BaseFreemarkerLanguageTemplateHelper implements FreemarkerLanguageTemplateHelper {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseFreemarkerLanguageTemplateHelper.class);
protected final TypeDefinition thisType;
protected final String protocolName;
protected final String flavorName;
protected final Map<String, TypeDefinition> types;
public static final TypeReference INT_TYPE_REFERENCE = new DefaultIntegerTypeReference(SimpleTypeReference.SimpleBaseType.INT, 32);
public TypeReference getIntTypeReference() {
return INT_TYPE_REFERENCE;
}
public static final TypeReference BOOL_TYPE_REFERENCE = new DefaultBooleanTypeReference();
public TypeReference getBoolTypeReference() {
return BOOL_TYPE_REFERENCE;
}
public static final TypeReference ANY_TYPE_REFERENCE = new DefaultUndefinedTypeReference();
public TypeReference getAnyTypeReference() {
return ANY_TYPE_REFERENCE;
}
protected BaseFreemarkerLanguageTemplateHelper(TypeDefinition thisType, String protocolName, String flavorName, Map<String, TypeDefinition> types) {
this.thisType = thisType;
this.protocolName = protocolName;
this.flavorName = flavorName;
this.types = types;
}
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(ComplexTypeDefinition.class::isInstance)
.filter(typeDefinition -> !(typeDefinition instanceof DiscriminatedComplexTypeDefinition))
.collect(Collectors.toList());
}
/* *********************************************************************************
* Methods related to type-references.
**********************************************************************************/
protected EnumTypeDefinition getEnumTypeDefinition(TypeReference typeReference) {
NonSimpleTypeReference nonSimpleTypeReference = typeReference.asNonSimpleTypeReference().orElseThrow(
() -> new FreemarkerException("type reference for enum types must be of type non simple type"));
String typeName = nonSimpleTypeReference.getName();
final TypeDefinition typeDefinition = nonSimpleTypeReference.getTypeDefinition();
if (typeDefinition == null) {
throw new FreemarkerException("Couldn't find given enum type definition with name " + typeName);
}
// TODO: same here. It is named complex type reference but it references a enum...
if (!typeDefinition.isEnumTypeDefinition()) {
throw new FreemarkerException("Referenced type with name " + typeName + " is not an enum type");
}
return (EnumTypeDefinition) typeDefinition;
}
/**
* 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) {
// Enum types always have simple type references.
return getEnumTypeDefinition(typeReference).getType().orElseThrow();
}
public SimpleTypeReference getEnumFieldTypeReference(TypeReference typeReference, String constantName) {
return (SimpleTypeReference) getEnumTypeDefinition(typeReference).getConstantType(constantName);
}
/* *********************************************************************************
* Methods related to fields.
**********************************************************************************/
public boolean hasFieldOfType(String fieldTypeName) {
Objects.requireNonNull(fieldTypeName);
if (thisType instanceof ComplexTypeDefinition) {
ComplexTypeDefinition complexTypeDefinition = (ComplexTypeDefinition) this.thisType;
return complexTypeDefinition.getFields().stream()
.anyMatch(field -> fieldTypeName.equals(field.getTypeName()));
}
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;
}
// TODO: check or describe why a instanceOf EnumField is not sufficient here
public boolean isEnumField(Field field) {
if (!(field instanceof TypedField)) {
return false;
}
TypedField typedField = (TypedField) field;
TypeReference typeReference = typedField.getType();
if (!typeReference.isNonSimpleTypeReference()) {
return false;
}
TypeDefinition typeDefinition = typeReference.asNonSimpleTypeReference().orElseThrow()
.getTypeDefinition();
return typeDefinition instanceof EnumTypeDefinition;
}
/* *********************************************************************************
* Methods related to terms and expressions.
**********************************************************************************/
protected int evaluateFixedValueExpression(Term term) {
Objects.requireNonNull(term);
final Expression expression = new ExpressionBuilder(term.stringRepresentation()).build();
return (int) expression.evaluate();
}
/* *********************************************************************************
* Methods related to discriminators.
**********************************************************************************/
/**
* 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)
SwitchField switchField = null;
Function<String, TypeReference> typeRefRetriever = null;
if (thisType.isDiscriminatedComplexTypeDefinition()) {
ComplexTypeDefinition parentType = thisType.asDiscriminatedComplexTypeDefinition().orElseThrow().getParentType().orElseThrow();
switchField = parentType.getSwitchField().orElse(null);
typeRefRetriever = propertyName -> parentType.getTypeReferenceForProperty(propertyName).orElse(null);
} else if (thisType.isDataIoTypeDefinition()) {
final DefaultDataIoTypeDefinition dataIoTypeDefinition = (DefaultDataIoTypeDefinition) this.thisType;
switchField = dataIoTypeDefinition.getSwitchField().orElseThrow();
typeRefRetriever = propertyName -> thisType.getParserArguments()
.orElse(Collections.emptyList())
.stream()
.filter(argument -> argument.getName().equals(propertyName))
.findFirst()
.map(Argument::getType)
.orElse(null);
} else if (thisType.isComplexTypeDefinition()) {
switchField = ((ComplexTypeDefinition) thisType).getSwitchField().orElse(null);
typeRefRetriever = propertyName -> ((ComplexTypeDefinition) thisType).getTypeReferenceForProperty(propertyName).orElse(null);
}
// Get the typeSwitch field from that.
if (switchField == null) {
return Collections.emptyMap();
}
Map<String, TypeReference> discriminatorTypes = new TreeMap<>();
for (VariableLiteral variableLiteral : switchField.getDiscriminatorExpressions()) {
// Get some symbolic name we can use.
String discriminatorName = variableLiteral.getDiscriminatorName();
final TypeReference typeReference = typeRefRetriever.apply(variableLiteral.getName());
Optional<TypeReference> discriminatorType = typeReference.getDiscriminatorType(variableLiteral);
if (discriminatorType.isEmpty()) {
throw new RuntimeException("no type for " + discriminatorName);
}
discriminatorTypes.put(discriminatorName, discriminatorType.orElse(null));
}
return discriminatorTypes;
}
public TypeReference getArgumentType(TypeReference typeReference, int index) {
Objects.requireNonNull(typeReference, "type reference must not be null");
NonSimpleTypeReference complexTypeReference = typeReference.asNonSimpleTypeReference().orElseThrow(() -> new FreemarkerException("Only non simple type references supported here."));
return complexTypeReference.getArgumentType(index);
}
public boolean discriminatorValueNeedsStringEqualityCheck(Term term) {
return discriminatorValueNeedsStringEqualityCheck(term, thisType);
}
public boolean discriminatorValueNeedsStringEqualityCheck(Term term, TypeDefinition typeDefinition) {
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 (typeDefinition instanceof ComplexTypeDefinition) {
ComplexTypeDefinition complexTypeDefinition = (ComplexTypeDefinition) typeDefinition;
boolean found = false;
for (Field field : complexTypeDefinition.getFields()) {
if (field instanceof NamedField) {
if (((NamedField) field).getName().equals(variableLiteral.getName())) {
if (field instanceof TypedField) {
TypedField typedField = (TypedField) field;
TypeReference type = typedField.getType();
found = (type instanceof StringTypeReference) || (type instanceof VstringTypeReference);
break;
}
}
}
}
if (found) {
return true;
}
}
for (Argument argument : typeDefinition.getParserArguments()
.orElse(Collections.emptyList())) {
if (argument.getName().equals(variableLiteral.getName())) {
TypeReference type = argument.getType();
return (type instanceof StringTypeReference) || (type instanceof VstringTypeReference);
}
}
return false;
}
public Collection<EnumValue> getUniqueEnumValues(List<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(List<EnumValue> enumValues, String constantName) {
Map<String, EnumValue> filteredEnumValues = new TreeMap<>();
for (EnumValue enumValue : enumValues) {
String key = enumValue.getConstant(constantName).orElseThrow(() -> new FreemarkerException("No constant name " + constantName + " found in enum value" + enumValue));
if (!filteredEnumValues.containsKey(key)) {
filteredEnumValues.put(key, enumValue);
}
}
return filteredEnumValues.values();
}
public SimpleTypeReference getEnumFieldSimpleTypeReference(NonSimpleTypeReference type, String fieldName) {
if (!(type.getTypeDefinition() instanceof EnumTypeDefinition)
|| !(((EnumTypeDefinition) type.getTypeDefinition()).getConstantType(fieldName) instanceof SimpleTypeReference)) {
throw new IllegalArgumentException("not an enum type or enum constant is not a simple type");
}
return (SimpleTypeReference) ((EnumTypeDefinition) type.getTypeDefinition()).getConstantType(fieldName);
}
/**
* Confirms if a variable is an implicit variable. These need to be handled differently when serializing and parsing.
*
* @param variableLiteral The variable to search for.
* @return boolean returns true if the variable's name is an implicit field
*/
protected boolean isVariableLiteralImplicitField(VariableLiteral variableLiteral) {
return thisType.asComplexTypeDefinition()
.map(complexTypeDefinition -> complexTypeDefinition.isVariableLiteralImplicitField(variableLiteral))
.orElse(false);
}
/**
* Confirms if a variable is an virtual variable. These need to be handled differently when serializing and parsing.
*
* @param variableLiteral The variable to search for.
* @return boolean returns true if the variable's name is an virtual field
*/
protected boolean isVariableLiteralVirtualField(VariableLiteral variableLiteral) {
return thisType.asComplexTypeDefinition()
.map(complexTypeDefinition -> complexTypeDefinition.isVariableLiteralVirtualField(variableLiteral))
.orElse(false);
}
/**
* Returns the implicit field that has the same name as the variable. These need to be handled differently when serializing and parsing.
*
* @param variableLiteral The variable to search for.
* @return ImplicitField returns the implicit field that corresponds to the variable's name.
*/
protected ImplicitField getReferencedImplicitField(VariableLiteral variableLiteral) {
return thisType.asComplexTypeDefinition()
.map(complexTypeDefinition -> complexTypeDefinition.getReferencedImplicitField(variableLiteral))
.orElse(null);
}
// TODO: replace that with term.isWildcard() (once the referenced wildcard term from build utils is used)
public boolean isWildcard(Term term) {
return term instanceof WildcardTerm;
}
/**
* can be used to throw a exception from the template
*
* @param message the message
* @return the exception
*/
public Supplier<FreemarkerException> fail(String message) {
return () -> new FreemarkerException(message);
}
public void info(String message, Object... objects) {
LOGGER.info(message, objects);
}
}
↑ V6008 Potential null dereference of 'typeRefRetriever'.