Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ public static TranslatorConfigurationException missingTypeMapping(Class<?> type,
return new TranslatorConfigurationException(ErrorCode.MISSING_TYPE_MAPPING, MISSING_TYPE_MAPPING, type.getName(), mapName);
}

public static TranslatorConfigurationException missingTypeMapping(String typeName, String mapName) {
return new TranslatorConfigurationException(ErrorCode.MISSING_TYPE_MAPPING, MISSING_TYPE_MAPPING, typeName, mapName);
}

public static TranslatorConfigurationException missingTypeAnnotation(Class<?> type) {
return new TranslatorConfigurationException(ErrorCode.MISSING_TYPE_ANNOTATION, MISSING_TYPE_ANNOTATION, type.getName());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ public class TypeMismatchException extends EfxCompilationException {
public enum ErrorCode {
CANNOT_CONVERT,
CANNOT_COMPARE,
EXPECTED_SCALAR,
EXPECTED_FIELD_CONTEXT
FIELD_MAY_REPEAT,
NODE_CONTEXT_AS_VALUE,
IDENTIFIER_IS_SEQUENCE,
IDENTIFIER_IS_SCALAR,
DICTIONARY_IS_SCALAR
}

private static final String CANNOT_CONVERT = "Type mismatch. Expected %s instead of %s.";
private static final String CANNOT_COMPARE = "Type mismatch. Cannot compare values of different types: %s and %s";
private static final String EXPECTED_SCALAR = "Type mismatch. Field '%s' may return multiple values from context '%s', but is used as a scalar. Use a sequence expression or change the context.";
private static final String EXPECTED_FIELD_CONTEXT = "Type mismatch. Context variable '$%s' refers to node '%s', but is used as a value. Only field context variables can be used in value expressions.";
private static final String CANNOT_COMPARE = "Type mismatch. Cannot compare values of different types: %s and %s.";
private static final String FIELD_MAY_REPEAT = "Type mismatch. Field '%s' may return multiple values from context '%s', but is used as a scalar. Use a sequence expression or change the context.";
private static final String NODE_CONTEXT_AS_VALUE = "Type mismatch. Context variable '$%s' refers to node '%s', but is used as a value. Only field context variables can be used in value expressions.";
private static final String IDENTIFIER_IS_SEQUENCE = "Type mismatch. Variable '$%s' is declared as a sequence, but is used as a scalar.";
private static final String IDENTIFIER_IS_SCALAR = "Type mismatch. Variable '$%s' is declared as a scalar, but is used where a sequence is expected. To use it as a single-element sequence, wrap it in square brackets: [$%s].";
private static final String DICTIONARY_IS_SCALAR = "Type mismatch. Dictionary lookup '$%s' returns a scalar, but is used where a sequence is expected. To use it as a single-element sequence, wrap it in square brackets: [$%s['key']].";

private final ErrorCode errorCode;

Expand Down Expand Up @@ -73,11 +79,23 @@ public static TypeMismatchException cannotCompare(ParserRuleContext ctx, Express
}

public static TypeMismatchException fieldMayRepeat(ParserRuleContext ctx, String fieldId, String contextSymbol) {
return new TypeMismatchException(ErrorCode.EXPECTED_SCALAR, ctx, EXPECTED_SCALAR, fieldId,
return new TypeMismatchException(ErrorCode.FIELD_MAY_REPEAT, ctx, FIELD_MAY_REPEAT, fieldId,
contextSymbol != null ? contextSymbol : "root");
}

public static TypeMismatchException nodesHaveNoValue(ParserRuleContext ctx, String variableName, String nodeId) {
return new TypeMismatchException(ErrorCode.EXPECTED_FIELD_CONTEXT, ctx, EXPECTED_FIELD_CONTEXT, variableName, nodeId);
public static TypeMismatchException nodeContextUsedAsValue(ParserRuleContext ctx, String variableName, String nodeId) {
return new TypeMismatchException(ErrorCode.NODE_CONTEXT_AS_VALUE, ctx, NODE_CONTEXT_AS_VALUE, variableName, nodeId);
}

public static TypeMismatchException identifierIsSequence(ParserRuleContext ctx, String variableName) {
return new TypeMismatchException(ErrorCode.IDENTIFIER_IS_SEQUENCE, ctx, IDENTIFIER_IS_SEQUENCE, variableName);
}

public static TypeMismatchException identifierIsScalar(ParserRuleContext ctx, String variableName) {
return new TypeMismatchException(ErrorCode.IDENTIFIER_IS_SCALAR, ctx, IDENTIFIER_IS_SCALAR, variableName, variableName);
}

public static TypeMismatchException dictionaryIsScalar(ParserRuleContext ctx, String dictionaryName) {
return new TypeMismatchException(ErrorCode.DICTIONARY_IS_SCALAR, ctx, DICTIONARY_IS_SCALAR, dictionaryName, dictionaryName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2026 European Union
*
* Licensed under the EUPL, Version 1.2 or – as soon they will be approved by the European
* Commission – subsequent versions of the EUPL (the "Licence"); You may not use this work except in
* compliance with the Licence. You may obtain a copy of the Licence at:
* https://joinup.ec.europa.eu/software/page/eupl
*
* Unless required by applicable law or agreed to in writing, software distributed under the Licence
* is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the Licence for the specific language governing permissions and limitations under
* the Licence.
*/
package eu.europa.ted.efx.model.types;

import static java.util.Map.entry;

import java.util.Map;

import eu.europa.ted.efx.exceptions.TranslatorConfigurationException;
import eu.europa.ted.efx.sdk2.EfxLexer;

/**
* Maps between different type name representations used in the EFX type system.
* Provides safe lookup methods that throw {@link TranslatorConfigurationException}
* when a mapping is missing, instead of silently returning null.
*/
public final class EfxTypeTokenLookup {

public static final String TEXT = getLexerSymbol(EfxLexer.Text);
public static final String INDICATOR = getLexerSymbol(EfxLexer.Indicator);
public static final String NUMERIC = getLexerSymbol(EfxLexer.Number);
public static final String DATE = getLexerSymbol(EfxLexer.Date);
public static final String TIME = getLexerSymbol(EfxLexer.Time);
public static final String DURATION = getLexerSymbol(EfxLexer.Duration);

private static final Map<String, String> FROM_EFORMS_TYPE = Map.ofEntries(
entry(FieldTypes.ID.getName(), TEXT),
entry(FieldTypes.ID_REF.getName(), TEXT),
entry(FieldTypes.TEXT.getName(), TEXT),
entry(FieldTypes.TEXT_MULTILINGUAL.getName(), TEXT),
entry(FieldTypes.INDICATOR.getName(), INDICATOR),
entry(FieldTypes.AMOUNT.getName(), NUMERIC),
entry(FieldTypes.NUMBER.getName(), NUMERIC),
entry(FieldTypes.MEASURE.getName(), NUMERIC),
entry(FieldTypes.DURATION.getName(), DURATION),
entry(FieldTypes.CODE.getName(), TEXT),
entry(FieldTypes.INTERNAL_CODE.getName(), TEXT),
entry(FieldTypes.INTEGER.getName(), NUMERIC),
entry(FieldTypes.DATE.getName(), DATE),
entry(FieldTypes.ZONED_DATE.getName(), DATE),
entry(FieldTypes.TIME.getName(), TIME),
entry(FieldTypes.ZONED_TIME.getName(), TIME),
entry(FieldTypes.URL.getName(), TEXT),
entry(FieldTypes.PHONE.getName(), TEXT),
entry(FieldTypes.EMAIL.getName(), TEXT));

private static final Map<Class<? extends EfxDataType>, String> FROM_JAVA_TYPE = Map.ofEntries(
entry(EfxDataType.String.class, TEXT),
entry(EfxDataType.Boolean.class, INDICATOR),
entry(EfxDataType.Number.class, NUMERIC),
entry(EfxDataType.Duration.class, DURATION),
entry(EfxDataType.Date.class, DATE),
entry(EfxDataType.Time.class, TIME));

/**
* Resolves an eForms SDK field type name to the corresponding EFX type name.
*
* @throws TranslatorConfigurationException if the type is not mapped
*/
public static String fromEformsType(String eformsType) {
String result = FROM_EFORMS_TYPE.get(eformsType);
if (result == null) {
throw TranslatorConfigurationException.missingTypeMapping(eformsType, "EfxTypeTokenLookup.FROM_EFORMS_TYPE");
}
return result;
}

/**
* Resolves a Java EfxDataType class to the corresponding EFX type name.
*
* @throws TranslatorConfigurationException if the type is not mapped
*/
public static String fromJavaType(Class<? extends EfxDataType> javaType) {
String result = FROM_JAVA_TYPE.get(javaType);
if (result == null) {
throw TranslatorConfigurationException.missingTypeMapping(javaType, "EfxTypeTokenLookup.FROM_JAVA_TYPE");
}
return result;
}

private static String getLexerSymbol(int tokenType) {
return EfxLexer.VOCABULARY.getLiteralName(tokenType).replaceAll("^'|'$", "");
}

private EfxTypeTokenLookup() {}
}
11 changes: 6 additions & 5 deletions src/main/java/eu/europa/ted/efx/model/variables/Dictionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
*/
package eu.europa.ted.efx.model.variables;

import java.util.Objects;

import eu.europa.ted.efx.model.expressions.PathExpression;
import eu.europa.ted.efx.model.expressions.TypedExpression;
import eu.europa.ted.efx.model.expressions.scalar.StringExpression;
Expand All @@ -29,8 +31,7 @@ public class Dictionary extends Identifier {
public final Class<? extends TypedExpression> type;

public Dictionary(String dictionaryName, PathExpression pathExpression, StringExpression keyExpression) {

super(dictionaryName, keyExpression.getDataType());
super(dictionaryName, pathExpression.getDataType());
this.keyExpression = keyExpression;
this.pathExpression = pathExpression;
this.type = pathExpression.getClass();
Expand All @@ -42,9 +43,9 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Dictionary dictionary = (Dictionary) o;
return java.util.Objects.equals(keyExpression, dictionary.keyExpression)
&& java.util.Objects.equals(pathExpression, dictionary.pathExpression)
&& java.util.Objects.equals(type, dictionary.type);
return Objects.equals(keyExpression, dictionary.keyExpression)
&& Objects.equals(pathExpression, dictionary.pathExpression)
&& Objects.equals(type, dictionary.type);
}

@Override
Expand Down
Loading