diff --git a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/util/GetChildrenVisitor.java b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/util/GetChildrenVisitor.java new file mode 100644 index 000000000..1122e7afa --- /dev/null +++ b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/util/GetChildrenVisitor.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V. + * + * 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 + * + * 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.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.visitor.AssetAdministrationShellElementVisitor; +import org.eclipse.digitaltwin.aas4j.v3.model.AssetAdministrationShell; +import org.eclipse.digitaltwin.aas4j.v3.model.Environment; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; +import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; + +public class GetChildrenVisitor implements AssetAdministrationShellElementVisitor { + + private final List children = new ArrayList<>(); + private Environment environment; + + public GetChildrenVisitor() { + } + + public void reset() { + children.clear(); + } + + public GetChildrenVisitor(Environment environment) { + this.environment = environment; + } + + public List getChildren() { + return children; + } + + @Override + public void visit(Environment environment) { + children.addAll(environment.getAssetAdministrationShells()); + children.addAll(environment.getConceptDescriptions()); + children.addAll(environment.getSubmodels()); + } + + @Override + public void visit(AssetAdministrationShell assetAdministrationShell) { + List submodelIds = assetAdministrationShell.getSubmodels().stream() + .map(x -> x.getKeys().get(x.getKeys().size() - 1).getValue()) + .collect(Collectors.toList()); + if (environment != null) { + children.addAll(environment.getSubmodels().stream() + .filter(x -> submodelIds.contains(x.getId())) + .collect(Collectors.toList())); + } + } + + @Override + public void visit(Submodel submodel) { + children.addAll(submodel.getSubmodelElements()); + } + + @Override + public void visit(SubmodelElementCollection submodelElementCollection) { + children.addAll(submodelElementCollection.getValue()); + } + + @Override + public void visit(SubmodelElementList submodelElementList) { + children.addAll(submodelElementList.getValue()); + } +} diff --git a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/util/GetIdentifierVisitor.java b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/util/GetIdentifierVisitor.java new file mode 100644 index 000000000..e3df45436 --- /dev/null +++ b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/util/GetIdentifierVisitor.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V. + * + * 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 + * + * 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.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util; + +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.visitor.AssetAdministrationShellElementVisitor; +import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; +import org.eclipse.digitaltwin.aas4j.v3.model.Referable; + +public class GetIdentifierVisitor implements AssetAdministrationShellElementVisitor { + + private String identifier; + + public static String getIdentifier(Referable referable) { + GetIdentifierVisitor visitor = new GetIdentifierVisitor(); + visitor.visit(referable); + return visitor.getIdentifier(); + } + + public String getIdentifier() { + return identifier; + } + + @Override + public void visit(Referable referable) { + if (referable != null) { + identifier = referable.getIdShort(); + } + AssetAdministrationShellElementVisitor.super.visit(referable); + } + + @Override + public void visit(Identifiable identifiable) { + if (identifiable != null) { + identifier = identifiable.getId(); + } + AssetAdministrationShellElementVisitor.super.visit(identifiable); + } +} diff --git a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementVisitor.java b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementVisitor.java index d0e27b8f7..24eb8f9d5 100644 --- a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementVisitor.java +++ b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementVisitor.java @@ -58,6 +58,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; public interface AssetAdministrationShellElementVisitor { @@ -171,6 +172,8 @@ public default void visit(SubmodelElement submodelElement) { visit((Capability) submodelElement); } else if (SubmodelElementCollection.class.isAssignableFrom(type)) { visit((SubmodelElementCollection) submodelElement); + } else if (SubmodelElementList.class.isAssignableFrom(type)) { + visit((SubmodelElementList) submodelElement); } else if (Operation.class.isAssignableFrom(type)) { visit((Operation) submodelElement); } else if (EventElement.class.isAssignableFrom(type)) { @@ -231,8 +234,8 @@ public default void visit(Capability capability) { public default void visit(ConceptDescription conceptDescription) { } - public default void visit(DataSpecificationContent dataSpecificationContent) { - } + public default void visit(DataSpecificationContent dataSpecificationContent) { + } public default void visit(Entity entity) { } @@ -297,6 +300,9 @@ public default void visit(Submodel submodel) { public default void visit(SubmodelElementCollection submodelElementCollection) { } + public default void visit(SubmodelElementList submodelElementList) { + } + public default void visit(Resource resource) { } diff --git a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementWalkerVisitor.java b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementWalkerVisitor.java index 3c51c7700..f373b8620 100644 --- a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementWalkerVisitor.java +++ b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/internal/visitor/AssetAdministrationShellElementWalkerVisitor.java @@ -46,6 +46,7 @@ import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; public interface AssetAdministrationShellElementWalkerVisitor extends AssetAdministrationShellElementVisitor { @@ -129,7 +130,7 @@ public default void visit(HasSemantics hasSemantics) { return; } visit(hasSemantics.getSemanticId()); - hasSemantics.getSupplementalSemanticIds().forEach(x->visit(x)); + hasSemantics.getSupplementalSemanticIds().forEach(x -> visit(x)); AssetAdministrationShellElementVisitor.super.visit(hasSemantics); } @@ -208,36 +209,36 @@ public default void visit(Referable referable) { } @Override - public default void visit(LangStringNameType langString){ - if (langString == null){ + public default void visit(LangStringNameType langString) { + if (langString == null) { return; } AssetAdministrationShellElementVisitor.super.visit(langString); - }; + } @Override - public default void visit(LangStringPreferredNameTypeIec61360 langString){ - if (langString == null){ + public default void visit(LangStringPreferredNameTypeIec61360 langString) { + if (langString == null) { return; } AssetAdministrationShellElementVisitor.super.visit(langString); - }; + } @Override - public default void visit(LangStringDefinitionTypeIec61360 langString){ - if (langString == null){ + public default void visit(LangStringDefinitionTypeIec61360 langString) { + if (langString == null) { return; } AssetAdministrationShellElementVisitor.super.visit(langString); - }; + } @Override - public default void visit(LangStringTextType langString){ - if (langString == null){ + public default void visit(LangStringTextType langString) { + if (langString == null) { return; } AssetAdministrationShellElementVisitor.super.visit(langString); - }; + } @Override public default void visit(Reference reference) { @@ -315,6 +316,15 @@ public default void visit(SubmodelElementCollection submodelElementCollection) { AssetAdministrationShellElementVisitor.super.visit(submodelElementCollection); } + @Override + public default void visit(SubmodelElementList submodelElementList) { + if (submodelElementList == null) { + return; + } + submodelElementList.getValue().forEach(x -> visit(x)); + AssetAdministrationShellElementVisitor.super.visit(submodelElementList); + } + @Override public default void visit(Operation operation) { if (operation == null) { diff --git a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtils.java b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtils.java index 1621c2c92..fac5cfeb7 100644 --- a/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtils.java +++ b/dataformat-core/src/main/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtils.java @@ -15,36 +15,38 @@ */ package org.eclipse.digitaltwin.aas4j.v3.dataformat.core.util; -import com.google.common.reflect.TypeToken; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.deserialization.EnumDeserializer; -import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util.IdentifiableCollector; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util.MostSpecificTypeTokenComparator; import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.serialization.EnumSerializer; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.Identifiable; import org.eclipse.digitaltwin.aas4j.v3.model.Key; import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; -import org.eclipse.digitaltwin.aas4j.v3.model.Operation; import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; -import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import com.google.common.reflect.TypeToken; +import java.util.Map; +import java.util.Objects; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util.GetChildrenVisitor; +import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.internal.util.GetIdentifierVisitor; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; /** * Provides utility functions related to AAS @@ -53,26 +55,62 @@ public class AasUtils { private static final Logger log = LoggerFactory.getLogger(AasUtils.class); - private static final String REFERENCE_ELEMENT_DELIMITER = ", "; + private static final String REFERENCE_ELEMENT_DELIMITER = ", "; + + private static final Map REFERENCE_TYPE_REPRESENTATION = Map.of( + ReferenceTypes.EXTERNAL_REFERENCE, "ExternalRef", + ReferenceTypes.MODEL_REFERENCE, "ModelRef"); private AasUtils() { } - /** - * Formats a Reference as string - * - * @param reference - * Reference to serialize - * @return string representation of the reference for serialization, null if - * reference is null - */ - private static String asString(Reference reference) { - if (reference == null) { - return null; - } - return String.format("[%s]%s", reference.getType(), - reference.getKeys().stream().map(x -> String.format("(%s)%s", EnumSerializer.serializeEnumName(x.getType().name()), x.getValue())).collect(Collectors.joining(REFERENCE_ELEMENT_DELIMITER))); - } + /** + * Formats a Reference as string + * + * @param reference Reference to serialize + * @return string representation of the reference for serialization, null if reference is null + */ + public static String asString(Reference reference) { + return asString(reference, true, true); + } + + /** + * Serializes a {@link Reference} to string. + * + * @param reference the reference to serialize + * @param includeReferenceType if reference type information should be included + * @param includeReferredSemanticId if referred semanticId should be included + * @return the serialized reference or null if reference is null, reference.keys is null or reference does not + * contain any keys + */ + public static String asString(Reference reference, boolean includeReferenceType, boolean includeReferredSemanticId) { + if (Objects.isNull(reference) || Objects.isNull(reference.getKeys()) || reference.getKeys().isEmpty()) { + return null; + } + String result = ""; + if (includeReferenceType) { + String referredSemanticId = includeReferredSemanticId + ? asString(reference.getReferredSemanticId(), includeReferenceType, false) + : ""; + result = String.format("[%s%s]", + asString(reference.getType()), + (Objects.nonNull(referredSemanticId) && !referredSemanticId.isBlank()) ? String.format("- %s -", referredSemanticId) + : ""); + } + result += reference.getKeys().stream() + .map(x -> String.format("(%s)%s", + EnumSerializer.serializeEnumName(x.getType().name()), + x.getValue())) + .collect(Collectors.joining(", ")); + return result; + } + + private static String asString(ReferenceTypes referenceType) { + if (!REFERENCE_TYPE_REPRESENTATION.containsKey(referenceType)) { + throw new IllegalArgumentException(String.format("Unsupported reference type '%s'", referenceType)); + } + return REFERENCE_TYPE_REPRESENTATION.get(referenceType); + } /** * Creates a reference for an Identifiable instance using provided implementation types for reference and key @@ -128,9 +166,9 @@ public static KeyTypes referableToKeyType(Referable referable) { * @return a Java interface representing the provided KeyElements type or null if no matching Class/interface could * be found. It also returns abstract types like SUBMODEL_ELEMENT or DATA_ELEMENT */ - private static Class keyTypeToClass(KeyTypes key) { + private static Class keyTypeToClass(KeyTypes key) { return Stream.concat(ReflectionHelper.INTERFACES.stream(), ReflectionHelper.INTERFACES_WITHOUT_DEFAULT_IMPLEMENTATION.stream()) - .filter(x -> x.getSimpleName().equals(EnumSerializer.serializeEnumName(key.name()))) + .filter(x -> x.getSimpleName().equals(EnumSerializer.serializeEnumName(key.name()))) .findAny() .orElse(null); } @@ -188,36 +226,53 @@ public static Reference toReference(Reference parent, Referable element) { } /** - * Checks if two references are refering to the same element + * Checks if two references are refering to the same element ignoring referredSemanticId. * * @param ref1 reference 1 * @param ref2 reference 2 * @return returns true if both references are refering to the same element, otherwise false */ public static boolean sameAs(Reference ref1, Reference ref2) { + return sameAs(ref1, ref2, false); + } + + /** + * Checks if two references are referring to the same element. + * + * @param ref1 reference 1 + * @param ref2 reference 2 + * @param compareReferredSemanticId true if referredSemanticId should be compared, false otherwise + * @return returns true if both references are referring to the same element, otherwise false + */ + public static boolean sameAs(Reference ref1, Reference ref2, boolean compareReferredSemanticId) { boolean ref1Empty = ref1 == null || ref1.getKeys() == null || ref1.getKeys().isEmpty(); boolean ref2Empty = ref2 == null || ref2.getKeys() == null || ref2.getKeys().isEmpty(); + if (ref1Empty != ref2Empty) { + return false; + } + if (ref1.getType() != ref2.getType()) { + return false; + } + if (compareReferredSemanticId && !sameAs(ref1.getReferredSemanticId(), ref2.getReferredSemanticId())) { + return false; + } if (ref1Empty && ref2Empty) { return true; } - if (ref1Empty != ref2Empty) { + if (ref1.getKeys().size() != ref2.getKeys().size()) { return false; } - int keyLength = Math.min(ref1.getKeys().size(), ref2.getKeys().size()); - for (int i = 0; i < keyLength; i++) { - Key ref1Key = ref1.getKeys().get(ref1.getKeys().size() - (i + 1)); - Key ref2Key = ref2.getKeys().get(ref2.getKeys().size() - (i + 1)); - Class ref1Type = keyTypeToClass(ref1Key.getType()); - Class ref2Type = keyTypeToClass(ref2Key.getType()); - if ((ref1Type == null && ref2Type != null) - || (ref1Type != null && ref2Type == null)) { + for (int i = 0; i < ref1.getKeys().size(); i++) { + Key key1 = ref1.getKeys().get(ref1.getKeys().size() - (i + 1)); + Key key2 = ref2.getKeys().get(ref2.getKeys().size() - (i + 1)); + if (Objects.isNull(key1) != Objects.isNull(key2)) { return false; } - if (ref1Type != ref2Type) { - if (!(ref1Type.isAssignableFrom(ref2Type) - || ref2Type.isAssignableFrom(ref1Type))) { - return false; - } + if (Objects.isNull(key1)) { + return true; + } + if (!Objects.equals(key1.getValue(), key2.getValue())) { + return false; } } return true; @@ -232,7 +287,7 @@ public static boolean sameAs(Reference ref1, Reference ref2) { * * @return the cloned reference */ - private static Reference clone(Reference reference, Class referenceType, Class keyType) { + private static Reference clone(Reference reference, Class referenceType, Class keyType) { if (reference == null || reference.getKeys() == null || reference.getKeys().isEmpty()) { return null; } @@ -278,114 +333,52 @@ public static Referable resolve(Reference reference, Environment env) { * @return returns an instance of T if the reference could successfully be resolved, otherwise null * @throws IllegalArgumentException if something goes wrong while resolving */ - @SuppressWarnings("unchecked") - public static T resolve(Reference reference, Environment env, Class type) { + @SuppressWarnings("unchecked") + public static T resolve(Reference reference, Environment env, Class type) { if (reference == null || reference.getKeys() == null || reference.getKeys().isEmpty()) { return null; } - Set identifiables = new IdentifiableCollector(env).collect(); - Object current = null; - int i = reference.getKeys().size() - 1; - if (type != null) { - Class actualType = keyTypeToClass(reference.getKeys().get(i).getType()); - if (actualType == null) { - log.warn("reference {} could not be resolved as key type has no known class.", - asString(reference)); - return null; - } - if (!type.isAssignableFrom(actualType)) { - log.warn("reference {} could not be resolved as target type is not assignable from actual type (target: {}, actual: {})", - asString(reference), type.getName(), actualType.getName()); - return null; - } - } - for (; i >= 0; i--) { + GetChildrenVisitor findChildrenVisitor = new GetChildrenVisitor(env); + findChildrenVisitor.visit(env); + Referable current = null; + for (int i = 0; i < reference.getKeys().size(); i++) { Key key = reference.getKeys().get(i); - Class referencedType = keyTypeToClass(key.getType()); - if (referencedType != null) { - List matchingIdentifiables = identifiables.stream() - .filter(x -> referencedType.isAssignableFrom(x.getClass())) - .filter(x -> x.getId().equals(key.getValue())) - .collect(Collectors.toList()); - if (matchingIdentifiables.size() > 1) { - throw new IllegalArgumentException("found multiple matching Identifiables for id '" + key.getValue() + "'"); + try { + int index = Integer.parseInt(key.getValue()); + if (Objects.isNull(current) || !SubmodelElementList.class.isAssignableFrom(current.getClass())) { + throw new IllegalArgumentException("reference uses index notation on an element that is not a SubmodelElementList"); } - if (matchingIdentifiables.size() == 1) { - current = matchingIdentifiables.get(0); - break; + List list = ((SubmodelElementList) current).getValue(); + if (list.size() <= index) { + throw new IllegalArgumentException(String.format( + "index notation out of bounds (list size: %s, requested index: %s)", + list.size(), + index)); } + current = list.get(index); + } catch (NumberFormatException e) { + current = findChildrenVisitor.getChildren().stream() + .filter(x -> Objects.equals(key.getValue(), GetIdentifierVisitor.getIdentifier(x))) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException(String.format( + "unable to resolve reference '%s' as element '%s' does not exist", + asString(reference), + key.getValue()))); } + findChildrenVisitor.reset(); + findChildrenVisitor.visit(current); } if (current == null) { return null; } - i++; - if (i == reference.getKeys().size()) { - return (T) current; - } - // follow idShort path until target - for (; i < reference.getKeys().size(); i++) { - Key key = reference.getKeys().get(i); - Class keyType = keyTypeToClass(key.getType()); - if (keyType != null) { - if (SubmodelElementList.class.isAssignableFrom(current.getClass())) { - try { - current = ((SubmodelElementList) current).getValue().get(Integer.parseInt(key.getValue())); - } catch (NumberFormatException ex) { - throw new IllegalArgumentException(String.format("invalid value for key with index %d, expected integer values >= 0, but found '%s'", - i, key.getValue())); - } catch (IndexOutOfBoundsException ex) { - throw new IllegalArgumentException(String.format("index out of bounds exception for key with index %d, expected integer values >= 0 and < %d, but found '%s'", - i, - ((SubmodelElementList) current).getValue().size(), - key.getValue())); - } - } else { - Collection collection; - if (Operation.class.isAssignableFrom(current.getClass())) { - Operation operation = (Operation) current; - collection = Stream.of(operation.getInputVariables().stream(), - operation.getOutputVariables().stream(), - operation.getInoutputVariables().stream()) - .flatMap(x -> x.map(y -> y.getValue())) - .collect(Collectors.toSet()); - } else { - List matchingProperties = getAasProperties(current.getClass()).stream() - .filter(x -> Collection.class.isAssignableFrom(x.getReadMethod().getReturnType())) - .filter(x -> TypeToken.of(x.getReadMethod().getGenericReturnType()) - .resolveType(Collection.class.getTypeParameters()[0]) - .isSupertypeOf(keyType)) - .collect(Collectors.toList()); - if (matchingProperties.isEmpty()) { - throw new IllegalArgumentException(String.format("error resolving reference - could not find matching property for type %s in class %s", - keyType.getSimpleName(), - current.getClass().getSimpleName())); - } - if (matchingProperties.size() > 1) { - throw new IllegalArgumentException(String.format("error resolving reference - found %d possible property paths for class %s (%s)", - matchingProperties.size(), - current.getClass().getSimpleName(), - matchingProperties.stream() - .map(x -> x.getName()) - .collect(Collectors.joining(", ")))); - } - try { - collection = (Collection) matchingProperties.get(0).getReadMethod().invoke(current); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { - throw new IllegalArgumentException("error resolving reference", ex); - } - } - Optional next = collection.stream() - .filter(x -> ((Referable) x).getIdShort().equals(key.getValue())) - .findFirst(); - if (next.isEmpty()) { - throw new IllegalArgumentException("error resolving reference - could not find idShort " + key.getValue()); - } - current = next.get(); - } - } + if (!type.isAssignableFrom(current.getClass())) { + throw new IllegalArgumentException(String.format( + "reference '%s' could not be resolved as target type is not assignable from actual type (target: %s, actual: %s)", + asString(reference), + type.getName(), + current.getClass().getName())); } - return (T) current; + return type.cast(current); } /** @@ -396,7 +389,7 @@ public static T resolve(Reference reference, Environment e * @return a list of all properties defined in any of AAS interface implemented by type. If type does not implement * any AAS interface an empty list is returned. */ - private static List getAasProperties(Class type) { + private static List getAasProperties(Class type) { Class aasType = ReflectionHelper.getAasInterface(type); if (aasType == null) { aasType = ReflectionHelper.INTERFACES_WITHOUT_DEFAULT_IMPLEMENTATION.stream() @@ -423,4 +416,4 @@ private static List getAasProperties(Class type) { .sorted(Comparator.comparing(x -> x.getName())) .collect(Collectors.toList()); } -} \ No newline at end of file +} diff --git a/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/Examples.java b/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/Examples.java index 4a0fc95f8..941d292d6 100644 --- a/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/Examples.java +++ b/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/Examples.java @@ -1,5 +1,5 @@ /* - * Copyright 2023 jab. + * Copyright (c) 2023 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtilsTest.java b/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtilsTest.java index 1659d2e5b..247888ff4 100644 --- a/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtilsTest.java +++ b/dataformat-core/src/test/java/org/eclipse/digitaltwin/aas4j/v3/dataformat/core/util/AasUtilsTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2021 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e. V. + * Copyright (C) 2023 SAP SE or an SAP affiliate company. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,27 +20,33 @@ import org.eclipse.digitaltwin.aas4j.v3.dataformat.core.AASFull; import org.eclipse.digitaltwin.aas4j.v3.model.Environment; import org.eclipse.digitaltwin.aas4j.v3.model.KeyTypes; +import org.eclipse.digitaltwin.aas4j.v3.model.Operation; import org.eclipse.digitaltwin.aas4j.v3.model.Referable; import org.eclipse.digitaltwin.aas4j.v3.model.Reference; +import org.eclipse.digitaltwin.aas4j.v3.model.ReferenceTypes; import org.eclipse.digitaltwin.aas4j.v3.model.Submodel; import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElement; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementCollection; +import org.eclipse.digitaltwin.aas4j.v3.model.SubmodelElementList; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultEnvironment; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultKey; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperation; -import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultOperationVariable; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultProperty; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultReference; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodel; +import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementCollection; import org.eclipse.digitaltwin.aas4j.v3.model.impl.DefaultSubmodelElementList; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.ArrayList; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @RunWith(JUnitParamsRunner.class) public class AasUtilsTest { + @Test public void whenResolve_withProperty_success() { String submodelId = "http://example.org/submodel"; @@ -68,38 +75,124 @@ public void whenResolve_withProperty_success() { Assert.assertEquals(expected, actual); } + @Test(expected = IllegalArgumentException.class) + public void whenResolve_withInvalidType_fail() { + String submodelId = "http://example.org/submodel"; + String submodelElementIdShort = "foo"; + SubmodelElement expected = new DefaultProperty.Builder() + .idShort(submodelElementIdShort) + .value("bar") + .build(); + Environment environment = new DefaultEnvironment.Builder() + .submodels(new DefaultSubmodel.Builder() + .id(submodelId) + .submodelElements(expected) + .build()) + .build(); + Reference reference = new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value(submodelId) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL_ELEMENT) + .value(submodelElementIdShort) + .build()) + .build(); + AasUtils.resolve(reference, environment, Operation.class); + } + @Test - public void whenResolve_withSubmodel_success() { - assertNotNull(AasUtils.resolve(AASFull.AAS_1.getSubmodels().get(0), AASFull.createEnvironment())); + public void whenResolve_insideSubmodelElementList_success() { + String submodelId = "http://example.org/submodel"; + String submodelElementIdShort = "foo"; + String submodelElementListIdShort = "list"; + SubmodelElement expected = new DefaultProperty.Builder() + .idShort(submodelElementIdShort) + .value("bar") + .build(); + SubmodelElementList list = new DefaultSubmodelElementList.Builder() + .idShort(submodelElementListIdShort) + .value(expected) + .build(); + Environment environment = new DefaultEnvironment.Builder() + .submodels(new DefaultSubmodel.Builder() + .id(submodelId) + .submodelElements(list) + .build()) + .build(); + Reference reference = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value(submodelId) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL_ELEMENT_LIST) + .value(submodelElementListIdShort) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL_ELEMENT) + .value("0") + .build()) + .build(); + Referable actual = AasUtils.resolve(reference, environment); + Assert.assertEquals(expected, actual); + } - Submodel asSubmodel = AasUtils.resolve( - AASFull.AAS_1.getSubmodels().get(0), - AASFull.createEnvironment(), - Submodel.class - ); - - assertNotNull(asSubmodel); - assertEquals(DefaultSubmodel.class, asSubmodel.getClass()); + @Test(expected = IllegalArgumentException.class) + public void whenResolve_insideSubmodelElementList_indexOutOfBounds() { + String submodelId = "http://example.org/submodel"; + String submodelElementIdShort = "foo"; + String submodelElementListIdShort = "list"; + SubmodelElement expected = new DefaultProperty.Builder() + .idShort(submodelElementIdShort) + .value("bar") + .build(); + SubmodelElementList list = new DefaultSubmodelElementList.Builder() + .idShort(submodelElementListIdShort) + .value(expected) + .build(); + Environment environment = new DefaultEnvironment.Builder() + .submodels(new DefaultSubmodel.Builder() + .id(submodelId) + .submodelElements(list) + .build()) + .build(); + Reference reference = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value(submodelId) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL_ELEMENT_LIST) + .value(submodelElementListIdShort) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL_ELEMENT) + .value("1") + .build()) + .build(); + AasUtils.resolve(reference, environment); } - + @Test - public void whenResolve_withOperation_success() { + public void whenResolve_insideSubmodelElementCollection_success() { String submodelId = "http://example.org/submodel"; String submodelElementIdShort = "foo"; - String submodelElement2IdShort = "bar"; + String submodelElementListIdShort = "list"; SubmodelElement expected = new DefaultProperty.Builder() - .idShort(submodelElement2IdShort) + .idShort(submodelElementIdShort) .value("bar") .build(); + SubmodelElementCollection list = new DefaultSubmodelElementCollection.Builder() + .idShort(submodelElementListIdShort) + .value(expected) + .build(); Environment environment = new DefaultEnvironment.Builder() .submodels(new DefaultSubmodel.Builder() .id(submodelId) - .submodelElements(new DefaultOperation.Builder() - .idShort(submodelElementIdShort) - .inputVariables(new DefaultOperationVariable.Builder() - .value(expected) - .build()) - .build()) + .submodelElements(list) .build()) .build(); Reference reference = new DefaultReference.Builder() @@ -108,18 +201,29 @@ public void whenResolve_withOperation_success() { .value(submodelId) .build()) .keys(new DefaultKey.Builder() - .type(KeyTypes.SUBMODEL_ELEMENT) - .value(submodelElementIdShort) + .type(KeyTypes.SUBMODEL_ELEMENT_LIST) + .value(submodelElementListIdShort) .build()) .keys(new DefaultKey.Builder() .type(KeyTypes.SUBMODEL_ELEMENT) - .value(submodelElement2IdShort) + .value(submodelElementIdShort) .build()) .build(); Referable actual = AasUtils.resolve(reference, environment); Assert.assertEquals(expected, actual); } + @Test + public void whenResolve_withSubmodel_success() { + Environment environment = AASFull.createEnvironment(); + Reference submodelRef = AASFull.AAS_1.getSubmodels().get(0); + Submodel expected = AASFull.SUBMODEL_3; + Referable asReferable = AasUtils.resolve(submodelRef, environment); + assertEquals(expected, asReferable); + Submodel asSubmodel = AasUtils.resolve(submodelRef, environment, Submodel.class); + assertEquals(expected, asSubmodel); + } + @Test public void whenResolve_withElementWithinSubmodelElementList_success() { String submodelId = "http://example.org/submodel"; @@ -154,4 +258,251 @@ public void whenResolve_withElementWithinSubmodelElementList_success() { Assert.assertEquals(expected, actual); } + @Test + public void whenSameAs_withDifferentKeyTypesButSameValues_success() { + String value = "0173-1#01-ADS698#010"; + Reference ref1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Reference ref2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.FRAGMENT_REFERENCE) + .value(value) + .build()) + .build(); + Assert.assertTrue(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withoutKeys_success() { + Reference ref1 = new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE).build(); + ref1.setKeys(null); + Reference ref2 = new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE).build(); + ref2.setKeys(new ArrayList<>()); + Assert.assertTrue(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withoutKeysAndDifferentTypes_fail() { + Reference ref1 = new DefaultReference.Builder().type(ReferenceTypes.EXTERNAL_REFERENCE).build(); + ref1.setKeys(null); + Reference ref2 = new DefaultReference.Builder().type(ReferenceTypes.MODEL_REFERENCE).build(); + ref2.setKeys(new ArrayList<>()); + Assert.assertFalse(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withoutKeysAndDifferentSemaniticIDs_fail() { + Reference semanticId1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .value("value1") + .build()) + .build(); + Reference semanticId2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.FRAGMENT_REFERENCE) + .value("value2") + .build()) + .build(); + Reference ref1 = new DefaultReference.Builder() + .referredSemanticId(semanticId1) + .build(); + ref1.setKeys(null); + Reference ref2 = new DefaultReference.Builder() + .referredSemanticId(semanticId2) + .build(); + ref2.setKeys(new ArrayList<>()); + Assert.assertFalse(AasUtils.sameAs(ref1, ref2, true)); + } + + @Test + public void whenSameAs_withDifferentKeyTypesButSameValuesAndSemanticIDs_success() { + String value = "0173-1#01-ADS698#010"; + Reference semanticId1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Reference semanticId2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.FRAGMENT_REFERENCE) + .value(value) + .build()) + .build(); + Reference ref1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .referredSemanticId(semanticId1) + .build(); + Reference ref2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.FRAGMENT_REFERENCE) + .value(value) + .build()) + .referredSemanticId(semanticId2) + .build(); + Assert.assertTrue(AasUtils.sameAs(ref1, ref2, true)); + } + + @Test + public void whenSameAs_withDifferentKeyTypesAndValues_fail() { + Reference ref1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("foo") + .build()) + .build(); + Reference ref2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.FRAGMENT_REFERENCE) + .value("bar") + .build()) + .build(); + Assert.assertFalse(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withSameKeyTypesAndValues_success() { + String value = "0173-1#01-ADS698#010"; + Reference ref1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Reference ref2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Assert.assertTrue(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withSameKeyTypesButDifferentValues_fail() { + Reference ref1 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("foo") + .build()) + .build(); + Reference ref2 = new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("bar") + .build()) + .build(); + Assert.assertFalse(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withDifferentReferredSemanticId_success() { + String value = "0173-1#01-ADS698#010"; + Reference ref1 = new DefaultReference.Builder() + .referredSemanticId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("foo") + .build()) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Reference ref2 = new DefaultReference.Builder() + .referredSemanticId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("bar") + .build()) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Assert.assertTrue(AasUtils.sameAs(ref1, ref2)); + } + + @Test + public void whenSameAs_withDifferentReferredSemanticId_fail() { + String value = "0173-1#01-ADS698#010"; + Reference ref1 = new DefaultReference.Builder() + .referredSemanticId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("foo") + .build()) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Reference ref2 = new DefaultReference.Builder() + .referredSemanticId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("bar") + .build()) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + Assert.assertFalse(AasUtils.sameAs(ref1, ref2, true)); + } + + @Test + public void whenAsString_withSubmodelElementList_success() { + Reference reference = new DefaultReference.Builder() + .type(ReferenceTypes.MODEL_REFERENCE) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL) + .value("submodel") + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.SUBMODEL_ELEMENT_LIST) + .value("list") + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.PROPERTY) + .value("0") + .build()) + .build(); + String expected = "[ModelRef](Submodel)submodel, (SubmodelElementList)list, (Property)0"; + String actual = AasUtils.asString(reference); + Assert.assertEquals(expected, actual); + } + + @Test + public void whenAsString_withReferredSemanticId_success() { + String value = "0173-1#01-ADS698#010"; + Reference reference = new DefaultReference.Builder() + .type(ReferenceTypes.EXTERNAL_REFERENCE) + .referredSemanticId(new DefaultReference.Builder() + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value("foo") + .build()) + .type(ReferenceTypes.MODEL_REFERENCE) + .build()) + .keys(new DefaultKey.Builder() + .type(KeyTypes.GLOBAL_REFERENCE) + .value(value) + .build()) + .build(); + String expected = "[ExternalRef- [ModelRef](GlobalReference)foo -](GlobalReference)0173-1#01-ADS698#010"; + String actual = AasUtils.asString(reference); + Assert.assertEquals(expected, actual); + } }