diff options
author | Frank Schnicke | 2018-08-29 14:29:01 +0000 |
---|---|---|
committer | Frank Schnicke | 2018-08-30 14:13:56 +0000 |
commit | 3c36acc5adb4ff8255dc6b68080fcaad814d267d (patch) | |
tree | 95a0b83b945d28e08a264f8330660588435f2b63 | |
parent | ad5285fcf3caf5a5173a15e2c4907179c6e6c00b (diff) | |
download | basyx-3c36acc5adb4ff8255dc6b68080fcaad814d267d.tar.gz basyx-3c36acc5adb4ff8255dc6b68080fcaad814d267d.tar.xz basyx-3c36acc5adb4ff8255dc6b68080fcaad814d267d.zip |
Adds creation of properties based on annotations
* This allows to automatically generate submodels from
their class description without the need for explicit
creation of the property by the class maintainer
Change-Id: I524f246c247fd6c7d4e934390023ebbd04509b3e
Signed-off-by: Frank Schnicke <frank.schnicke@iese.fraunhofer.de>
7 files changed, 394 insertions, 40 deletions
diff --git a/sdks/java/basys.sdk/regression/org/eclipse/basyx/testsuite/support/backend/common/stubs/java/submodel/Stub1SubmodelType.java b/sdks/java/basys.sdk/regression/org/eclipse/basyx/testsuite/support/backend/common/stubs/java/submodel/Stub1SubmodelType.java index dc8ea0490..95beec279 100644 --- a/sdks/java/basys.sdk/regression/org/eclipse/basyx/testsuite/support/backend/common/stubs/java/submodel/Stub1SubmodelType.java +++ b/sdks/java/basys.sdk/regression/org/eclipse/basyx/testsuite/support/backend/common/stubs/java/submodel/Stub1SubmodelType.java @@ -1,17 +1,19 @@ package org.eclipse.basyx.testsuite.support.backend.common.stubs.java.submodel; import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; import org.eclipse.basyx.aas.api.annotation.AASOperation; +import org.eclipse.basyx.aas.api.annotation.AASProperty; import org.eclipse.basyx.aas.api.resources.basic.IAssetAdministrationShell; +import org.eclipse.basyx.aas.impl.provider.DescriptorGenerator; import org.eclipse.basyx.aas.impl.resources.basic.AssetAdministrationShell; import org.eclipse.basyx.aas.impl.resources.basic.AssetKind; -import org.eclipse.basyx.aas.impl.resources.basic.CollectionProperty; import org.eclipse.basyx.aas.impl.resources.basic.DataType; import org.eclipse.basyx.aas.impl.resources.basic.Operation; import org.eclipse.basyx.aas.impl.resources.basic.ParameterType; import org.eclipse.basyx.aas.impl.resources.basic.PropertyContainer; -import org.eclipse.basyx.aas.impl.resources.basic.SingleProperty; import org.eclipse.basyx.aas.impl.resources.basic.SubModel; @@ -25,11 +27,24 @@ import org.eclipse.basyx.aas.impl.resources.basic.SubModel; public class Stub1SubmodelType extends SubModel { + /** * Property type with nested property values */ public class NestedPropertyType extends PropertyContainer { + + /** + * Sub model property "samplePropertyA" + */ + @AASProperty public int samplePropertyA = 4; + + /** + * Sub model property "samplePropertyB" + */ + @AASProperty public int samplePropertyB = 5; + + /** * Nested operation */ @@ -48,19 +63,6 @@ public class Stub1SubmodelType extends SubModel { setDataType(DataType.REFERENCE); parent.addProperty(this); - // Register contained properties - SingleProperty property4 = new SingleProperty(4); - property4.setName("samplePropertyA"); - property4.setId("samplePropertyA"); - property4.setDataType(DataType.INTEGER); - this.addProperty(property4); - - SingleProperty property5 = new SingleProperty(5); - property5.setName("samplePropertyB"); - property5.setId("samplePropertyB"); - property5.setDataType(DataType.INTEGER); - this.addProperty(property5); - // Create and add operation Operation op = new Operation(); op.setAssetKind(AssetKind.INSTANCE); @@ -72,9 +74,34 @@ public class Stub1SubmodelType extends SubModel { op.setParameterTypes(pt); op.setReturnDataType(DataType.INTEGER); this.addOperation(op); + + DescriptorGenerator.addProperties(this); } } + /** + * Sub model property "sampleProperty1" + */ + @AASProperty public int sampleProperty1 = 2; + + + /** + * Sub model property "sampleProperty2" + */ + @AASProperty public int sampleProperty2 = 3; + + + /** + * Sub model property "sampleProperty3" is nested property + */ + @AASProperty public NestedPropertyType sampleProperty3 = null; + + + /** + * Sub model property "sampleProperty4" + */ + @AASProperty public Collection<Integer> sampleProperty4 = new LinkedList<Integer>(); + @@ -101,32 +128,10 @@ public class Stub1SubmodelType extends SubModel { aas.setId("Stub1AAS"); aas.setName("Stub1AAS"); this.setParent(aas); + + sampleProperty3 = new NestedPropertyType("sampleProperty3", this); - - - // Create and add properties - SingleProperty property1 = new SingleProperty(2); - property1.setName("sampleProperty1"); - property1.setId("sampleProperty1"); - property1.setDataType(DataType.INTEGER); - this.addProperty(property1); - - SingleProperty property2 = new SingleProperty(3); - property2.setName("sampleProperty2"); - property2.setId("sampleProperty2"); - property2.setDataType(DataType.INTEGER); - this.addProperty(property2); - - NestedPropertyType sampleProperty3 = new NestedPropertyType("sampleProperty3", this); - this.addProperty(sampleProperty3); - - ArrayList<Object> collection = new ArrayList<>(); - collection.add(42); - CollectionProperty sampleProperty4 = new CollectionProperty(collection); - sampleProperty4.setName("sampleProperty4"); - sampleProperty4.setId("sampleProperty4"); - sampleProperty4.setDataType(DataType.COLLECTION); - this.addProperty(sampleProperty4); + DescriptorGenerator.addProperties(this); } diff --git a/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/provider/DescriptorGenerator.java b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/provider/DescriptorGenerator.java new file mode 100644 index 000000000..f81eab872 --- /dev/null +++ b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/provider/DescriptorGenerator.java @@ -0,0 +1,110 @@ +package org.eclipse.basyx.aas.impl.provider; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.eclipse.basyx.aas.api.annotation.AASProperty; +import org.eclipse.basyx.aas.impl.resources.basic.CollectionReflectionProperty; +import org.eclipse.basyx.aas.impl.resources.basic.DataType; +import org.eclipse.basyx.aas.impl.resources.basic.DataTypeMapping; +import org.eclipse.basyx.aas.impl.resources.basic.MapReflectionProperty; +import org.eclipse.basyx.aas.impl.resources.basic.Property; +import org.eclipse.basyx.aas.impl.resources.basic.PropertyContainer; +import org.eclipse.basyx.aas.impl.resources.basic.SingleReflectionProperty; +import org.eclipse.basyx.aas.impl.resources.basic.SubModel; + +/** + * Generates properties using the @AASProperty annotation + * + * @author schnicke + * + */ +public class DescriptorGenerator { + /** + * Get all fields that are tagged with AASProperty + */ + private static Collection<Field> getAllFields(Class<?> cls) { + // Return value + Collection<Field> result = new LinkedList<>(); + while (cls != null) { + try { + // Get fields + Field[] fields = cls.getDeclaredFields(); + + // Add fields to result + for (Field field : fields) + if (field.getAnnotation(AASProperty.class) != null) + result.add(field); + } catch (Exception e) { + // Do nothing + } + cls = cls.getSuperclass(); + } + + // Return result + return result; + } + + public static void addProperties(SubModel sm) { + for (Property p : generateProperty(sm)) { + sm.addProperty(p); + } + } + + public static void addProperties(PropertyContainer container) { + for (Property p : generateProperty(container)) { + container.addProperty(p); + } + } + + public static List<Property> generateProperty(Object obj) { + List<Property> ret = new ArrayList<>(); + Collection<Field> fields = getAllFields(obj.getClass()); + + // Iterate over the fields and assign the correct property type + // Uses reflection properties to allow modifying the standard values of the data + // in contrast to a standard property + for (Field f : fields) { + f.setAccessible(true); + Property p; + if (f.getType().isPrimitive() || isPrimitiveWrapper(f.getType()) || f.getType() == String.class) { + SingleReflectionProperty singleProp = new SingleReflectionProperty(f, obj); + DataType type = DataTypeMapping.map(obj.getClass()); + singleProp.setDataType(type); + p = singleProp; + } else if (Collection.class.isAssignableFrom(f.getType())) { + CollectionReflectionProperty collectionProp = new CollectionReflectionProperty(f, obj); + collectionProp.setDataType(DataType.COLLECTION); + p = collectionProp; + } else if (Map.class.isAssignableFrom(f.getType())) { + MapReflectionProperty mapProp = new MapReflectionProperty(f, obj); + p = mapProp; + } else if (PropertyContainer.class.isAssignableFrom(f.getType())) { + try { + p = (Property) f.get(obj); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + throw new RuntimeException("Reflection error with property " + f.getName() + " of type " + f.getType()); + } + } else { + throw new RuntimeException("Unknown property type " + f.getType()); + } + + // Set name and id independent of the property type + String name = f.getName(); + p.setName(name); + p.setId(name); + ret.add(p); + + } + return ret; + } + + private static boolean isPrimitiveWrapper(Class<?> c) { + return c == Integer.class|| c == Boolean.class || c == Double.class || c == Float.class || c == Character.class; + } +} diff --git a/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/AbstractReflectionProperty.java b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/AbstractReflectionProperty.java new file mode 100644 index 000000000..79952cd64 --- /dev/null +++ b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/AbstractReflectionProperty.java @@ -0,0 +1,28 @@ +package org.eclipse.basyx.aas.impl.resources.basic; + +import java.lang.reflect.Field; + + +/** + * Base class for properties relying on reflections + * @author schnicke + * + */ +public abstract class AbstractReflectionProperty extends Property { + Field f; + Object o; + + public AbstractReflectionProperty(Field f, Object o) { + super(); + this.f = f; + this.o = o; + } + + protected Field getField() { + return f; + } + + protected Object getObject() { + return o; + } +} diff --git a/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/CollectionReflectionProperty.java b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/CollectionReflectionProperty.java new file mode 100644 index 000000000..dbad40aac --- /dev/null +++ b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/CollectionReflectionProperty.java @@ -0,0 +1,69 @@ +package org.eclipse.basyx.aas.impl.resources.basic; + +import java.lang.reflect.Field; +import java.util.Collection; + +import org.eclipse.basyx.aas.api.exception.ServerException; +import org.eclipse.basyx.aas.api.exception.TypeMismatchException; +import org.eclipse.basyx.aas.api.resources.basic.ICollectionProperty; + +/** + * Provides reflective access to a collection based on a property + * + * @author schnicke + * + */ +public class CollectionReflectionProperty extends AbstractReflectionProperty implements ICollectionProperty { + + public CollectionReflectionProperty(Field f, Object o) { + super(f, o); + setCollection(true); + } + + @Override + public Object get(Object objRef) { + return null; // TODO: ? + } + + @Override + public void set(Collection<Object> collection) throws ServerException { + try { + f.set(getObject(), collection); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + throw new ServerException("Reflection failed"); + } + } + + @Override + public void add(Object newValue) throws ServerException, TypeMismatchException { + getCollection().add(newValue); + } + + @Override + public void remove(Object objectRef) throws ServerException { + getCollection().remove(objectRef); + } + + @Override + public Collection<Object> getElements() throws ServerException { + return getCollection(); + } + + @Override + public int getElementCount() throws ServerException { + return getCollection().size(); + + } + + @SuppressWarnings("unchecked") + private Collection<Object> getCollection() throws ServerException { + try { + return (Collection<Object>) f.get(getObject()); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + throw new ServerException("Reflection failed"); + } + } + +} diff --git a/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/DataTypeMapping.java b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/DataTypeMapping.java new file mode 100644 index 000000000..90dbc1b2a --- /dev/null +++ b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/DataTypeMapping.java @@ -0,0 +1,40 @@ +package org.eclipse.basyx.aas.impl.resources.basic; + +import java.util.Collection; +import java.util.Map; + +/** + * Maps a class to its corresponding DataType + * + * @author schnicke + * + */ +public class DataTypeMapping { + public static DataType map(Class<?> c) { + if (c.isPrimitive()) { + if (c == int.class || c == Integer.class) { + return DataType.INTEGER; + } else if (c == void.class || c == Void.class) { + return DataType.VOID; + } else if (c == boolean.class || c == Boolean.class) { + return DataType.BOOLEAN; + } else if (c == float.class || c == Float.class) { + return DataType.FLOAT; + } else if (c == double.class || c == Double.class) { + return DataType.DOUBLE; + } else if (c == char.class || c == Character.class) { + return DataType.CHARACTER; + } else { + throw new RuntimeException("Unsupported property type " + c); + } + } else if (c == String.class) { + return DataType.STRING; + } else if (Map.class.isAssignableFrom(c)) { + return DataType.MAP; + } else if (Collection.class.isAssignableFrom(c)) { + return DataType.COLLECTION; + } else { + return DataType.REFERENCE; + } + } +} diff --git a/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/MapReflectionProperty.java b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/MapReflectionProperty.java new file mode 100644 index 000000000..adfa4e299 --- /dev/null +++ b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/MapReflectionProperty.java @@ -0,0 +1,67 @@ +package org.eclipse.basyx.aas.impl.resources.basic; + +import java.lang.reflect.Field; +import java.util.Collection; +import java.util.Map; + +import org.eclipse.basyx.aas.api.exception.ServerException; +import org.eclipse.basyx.aas.api.exception.TypeMismatchException; +import org.eclipse.basyx.aas.api.resources.basic.IMapProperty; + +/** + * Provides reflective access to a map based on a property + * @author schnicke + * + */ +public class MapReflectionProperty extends AbstractReflectionProperty implements IMapProperty { + + public MapReflectionProperty(Field f, Object o) { + super(f, o); + } + + @Override + public Object getValue(Object key) throws TypeMismatchException, ServerException { + return getMap().get(key); + } + + @Override + public void put(Object key, Object value) throws ServerException { + getMap().put(key, value); + } + + @Override + public void set(Map<Object, Object> map) throws ServerException { + try { + f.set(getObject(), map); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + throw new ServerException("Reflection failed"); + } + } + + @Override + public Collection<Object> getKeys() throws TypeMismatchException, ServerException { + return getMap().keySet(); + } + + @Override + public Integer getEntryCount() throws TypeMismatchException, ServerException { + return getMap().size(); + } + + @Override + public void remove(Object key) throws ServerException, TypeMismatchException { + getMap().remove(key); + } + + @SuppressWarnings("unchecked") + private Map<Object, Object> getMap() throws ServerException { + try { + return (Map<Object, Object>) f.get(o); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + throw new ServerException("Reflection failed"); + } + } + +} diff --git a/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/SingleReflectionProperty.java b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/SingleReflectionProperty.java new file mode 100644 index 000000000..acef18836 --- /dev/null +++ b/sdks/java/basys.sdk/src/org/eclipse/basyx/aas/impl/resources/basic/SingleReflectionProperty.java @@ -0,0 +1,35 @@ +package org.eclipse.basyx.aas.impl.resources.basic; + +import java.lang.reflect.Field; + +import org.eclipse.basyx.aas.api.exception.ServerException; + +/** + * Provides reflective access to a simple value based on a property + * @author schnicke + * + */ +public class SingleReflectionProperty extends SingleProperty{ + private Field f; + private Object o; + public SingleReflectionProperty(Field f, Object o) { + super(null); + this.f = f; + this.o = o; + f.setAccessible(true); + } + + @Override + public Object get() throws Exception { + return f.get(o); + } + + @Override + public void set(Object newValue) throws ServerException { + try { + f.set(o, newValue); + } catch (IllegalArgumentException | IllegalAccessException e) { + e.printStackTrace(); + } + } +} |