blob: 746eca9fcbbff1e2818aa4f8d31a431dc62ec753 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2023 DFKI.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* DFKI - Tapanta Bhanja <tapanta.bhanja@dfki.de>
* CEA LIST - Saadia DHOUIB <saadia.dhouib@cea.fr>
*******************************************************************************/
package org.eclipse.aas.basyx.codegen.generator.submodel;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.aas.api.communications.Endpoint;
import org.eclipse.aas.api.communications.ProtocolKind;
import org.eclipse.aas.api.submodel.SubModel;
import org.eclipse.aas.api.submodel.submodelelement.Operation;
import org.eclipse.aas.api.submodel.submodelelement.SubModelElementCollection;
import org.eclipse.aas.api.submodel.submodelelement.dataelement.File;
import org.eclipse.aas.api.submodel.submodelelement.dataelement.IOperationVariable;
import org.eclipse.aas.api.submodel.submodelelement.dataelement.Property;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DEWorkspaceCreator {
private static final Logger logger = LoggerFactory.getLogger(DEWorkspaceCreator.class);
private SubModel subModel;
private String nameSpace;
private int year = Calendar.getInstance().get(Calendar.YEAR);
private List<Operation> operations;
private List<SubModelElementCollection> subModelSECs;
private List<Property> properties;
private List<File> files;
/**
* Constructor for the DEWorkspaceCreator Class. It receives a SubModel instance
* and the Namespace for the project to be generated as String.
*
* All other required variables and method calls undertaken automatically.
* The user basically initializes this class and calls the {@code generateDEWorkspace()} method
* to create a Dynamic Elements Workspace for each SubModel declared in the
* Asset Administration Shell.
*
* @param subModel
* @param nameSpace
*/
public DEWorkspaceCreator(SubModel subModel, String nameSpace) {
this.subModel = subModel;
this.nameSpace = nameSpace;
this.operations = subModel.getOperations();
this.subModelSECs = subModel.getSubModelElementCollections();
this.properties = subModel.getProperties();
this.files = subModel.getFiles();
}
/**
* Generates the String which contains the code for the Dynamic Elements Workspace
* (DEWorkspace) class.
*
* This DEWorkspace Class is created for each submodel created by the user for an
* Asset Administration Shell.
*
* @return Java code in String format for the Dynamic Elements Workspace for each Submodel.
*/
public String generateDEWorkspace() {
String text =
"/*******************************************************************************\n"
+ " * Copyright (c) " + year +" DFKI.\n"
+ " *\n"
+ " * This program and the accompanying materials\n"
+ " * are made available under the terms of the Eclipse Public License 2.0\n"
+ " * which accompanies this distribution, and is available at\n"
+ " * https://www.eclipse.org/legal/epl-2.0/\n"
+ " *\n"
+ " * SPDX-License-Identifier: EPL-2.0\n"
+ " *\n"
+ " * Contributors:\n"
+ " * DFKI - Tapanta Bhanja <tapanta.bhanja@dfki.de>\n"
+ " *******************************************************************************/\r\n" +
"package " + this.nameSpace + ".module.submodels." + this.subModel.getIdShort().toLowerCase() + ";\r\n" +
"import " + this.nameSpace + ".connection.ConnectedDevices;\r\n" +
"import " + this.nameSpace + ".module.ConceptDescriptions;\r\n" +
"\r\n" +
"import java.util.Collection; \r\n" +
"import java.util.LinkedList;\r\n" +
"import java.util.List;\r\n" +
"\r\n" +
"import java.math.BigInteger; \r\n" +
"\r\n" +
"import javax.xml.datatype.XMLGregorianCalendar;\r\n" +
"import javax.xml.datatype.Duration;\r\n" +
"import javax.xml.namespace.QName;\r\n" +
"\r\n" +
"import org.eclipse.basyx.submodel.metamodel.api.submodelelement.ISubmodelElement; \r\n" +
"import org.eclipse.basyx.vab.exception.provider.ProviderException; \r\n" +
"import org.eclipse.basyx.vab.protocol.opcua.types.NodeId;\r\n" +
"\r\n" +
"/**\r\n" +
" * \r\n" +
" * @author DFKI \r\n" +
" * \r\n" +
" * Edit this file for changing operation behaviours in the respective operation body. \r\n" +
" */\r\n" +
"\r\n" +
"public class " + "DynamicElementsWorkspace" + " {\r\n" +
" private ConnectedDevices connectedDevices;\r\n" +
" private ConceptDescriptions conceptDescriptions = new ConceptDescriptions();\r\n"
+ " \r\n"
+ " public "+ "DynamicElementsWorkspace" + " (ConnectedDevices connectedDevices) {\r\n"
+ " this.connectedDevices = connectedDevices; \r\n"
+ " }\r\n"
+ " \r\n"
+ "\r\n"
+ "\r\n";
List<Operation> retrievedOperations = new ArrayList<Operation>();
for (SubModelElementCollection sec : this.subModelSECs) {
retrievedOperations = retrieveOperations(sec);
}
for (Operation operation : this.operations) {
text+= generateOperationWS(operation);
text+= "\r\n";
}
for (Operation retrievedOperation : retrievedOperations) {
text += generateOperationWS(retrievedOperation);
text+="\r\n";
}
for (SubModelElementCollection sec : this.subModelSECs) {
text += generateSECWS(sec);
text+= "\r\n";
}
for (Property prop : this.properties) {
text += generatePropertyWS(prop);
text += "\r\n";
}
for (File file : this.files) {
text += generateFileWS(file);
text += "\r\n";
}
text+= "\r\n"
+ "}\r\n";
return text;
}
/**
* Generates the Methods for Operations declared for a SubModel in its
* respective Dynamic Elements Workspace {@code DEWorkspace} class.
*
* @param operation
* @return Operations' Methods generated as String.
*/
private String generateOperationWS(Operation operation) {
String immediateParent = "";
// Checking for Parents.
if (operation.getParentSEC()!=null) {
immediateParent = operation.getParentSEC().getIdShort();
}
else if (operation.getParentSub()!=null) {
immediateParent = operation.getParentSub().getIdShort();
}
else {
logger.error("Operation: " + operation.getIdShort()
+ "has no Parent defined");
}
List<IOperationVariable> inputVariables = operation.getInputVars();
List<IOperationVariable> inoutputVariables = operation.getInoutputVars();
List<IOperationVariable> outputVariables = operation.getOutputVars();
String returnStatement = "";
String inputVariableFormulation = "";
String operationMethodSignature = "";
if (operation.getOpCode().isEmpty()) {
if(outputVariables.isEmpty()) {
returnStatement += " \r\n";
}
else {
returnStatement += " return " + "(" + convertBaSyxToJavaTypes(outputVariables.get(0).getValueType()) + ") "
+ subModel.getIdShort() + "." + immediateParent + "_" + outputVariables.get(0).getValue().getIdShort() + "_"
+ operation.getIdShort() + "_" + "Output" + ".getValue();\r\n";
}
}
else {
;
}
if (!inputVariables.isEmpty()) {
for (IOperationVariable inputVariable : inputVariables) {
inputVariableFormulation += convertBaSyxToJavaTypes(inputVariable.getValueType()) + " " + inputVariable.getValue().getIdShort() + ",";
}
// Deleting the comma at the end of the String "InputVariableFormulation".
if (inputVariableFormulation != null && inputVariableFormulation.length() > 0 && inputVariableFormulation.charAt(inputVariableFormulation.length()-1) == ',') {
inputVariableFormulation = inputVariableFormulation.substring(0, inputVariableFormulation.length()-1);
}
}
else {
;
}
if (!inputVariables.isEmpty()) {
if(!outputVariables.isEmpty()) {
operationMethodSignature = "" + " public " + convertBaSyxToJavaTypes(outputVariables.get(0).getValueType()) + " " + immediateParent + "_" + operation.getIdShort() + "(" + inputVariableFormulation + ") {\r\n" + "\r\n";
}
else {
operationMethodSignature = "" + " public void " + immediateParent + "_" + operation.getIdShort() + "(" + inputVariableFormulation + ") {\r\n" + "\r\n";
}
}
else {
if(!outputVariables.isEmpty()) {
operationMethodSignature = "" + " public " + convertBaSyxToJavaTypes(outputVariables.get(0).getValueType()) + " " + immediateParent + "_" + operation.getIdShort() + "() {\r\n" + "\r\n";
}
else {
operationMethodSignature = "" + " public void " + immediateParent + "_" + operation.getIdShort() + "() {\r\n" + "\r\n";
}
}
// Formulating the example setValue for Output Variables.
String exampleOutputSet = "";
if (outputVariables!=null && !outputVariables.isEmpty()) {
exampleOutputSet += " * " + subModel.getIdShort() + "."
+ immediateParent + "_" + outputVariables.get(0).getValue().getIdShort()
+ "_" + operation.getIdShort()
+ "_" + "Output" + "."
+ "setValue(<<Value Instance>>);\r\n";
}
else {
exampleOutputSet = "";
}
// Formulating the Instruction set for Operation Methods in DEWorkspace.
String InstructionSet = ""
+ " /**\n"
+ " * [Note:] The method signature, if populated with parameters, are the \r\n"
+ " * Input and InOutput parameters assigned by the user to the Operation. \r\n"
+ " * \r\n"
+ " * Papyrus4Manufacturing handles InOutput parameters as Input Parameters. \r\n"
+ " * \n"
+ " * Please under no circumstances change/modify this method's Signature \r\n"
+ " * and the return statement. \r\n"
+ " * \n"
+ " * After this methods behaviour has been defined, please use the following \r\n"
+ " * code line (as an example) to assign the output variable of this Operation its value, \r\n"
+ " * if and only if an OutputVariable is defined for this Operation. \r\n"
+ " * \r\n"
+ exampleOutputSet
+ " * \n"
+ " */\n";
String opBodyText = InstructionSet
+ operationMethodSignature
+ returnStatement
+ "\r\n };\r\n";
return opBodyText;
}
/**
* Generates the methods for those Properties defined by user as "Dynamic".
* The output of these methods are then passed on to a {@code setGetHandler()} function
* in a {@code ValueDelegates} class written specifically for this Project.
*
* @param property
* @return Property's methods generated as String.
*/
private String generatePropertyWS(Property property) {
String immediateParent = "";
// Checking for Parents.
if (property.getParentSEC()!=null) {
immediateParent = property.getParentSEC().getIdShort();
}
else if (property.getParentSub()!=null) {
immediateParent = property.getParentSub().getIdShort();
}
else {
logger.error("Property: " + property.getIdShort()
+ "has no Parent defined");
}
String getterCode = "";
Class<? extends Object> identifierClass = null;
String InstructionSet = "";
if (property.isDynamic()) {
InstructionSet = ""
+ " /**\n"
+ " * [Note:] This method body is made available in order to define a behaviour for the property \r\n"
+ " * under consideration, since this property is declared as dynamic. Meaning its value is subjected \r\n"
+ " * to certain dynamism while being updated. \r\n"
+ " * Please under no circumstances change the method signature - i.e., Return Type, \r\n"
+ " * Method Name, Method Parameters and the Return Statement. \r\n"
+ " * All behaviours may be defined inside the method body with available \r\n"
+ " * parameters and one should abide by the return type for the method while \r\n"
+ " * computing the output. \r\n"
+ " * \r\n"
+ " * \n"
+ " */\n";
// ----- Getter Code Section ------
// Dynamic Property default value;
String defaultValue = "null";
// Determining the class of the NodeId Identifier:
try {
identifierClass = property.getIdentifier().getClass();
}
catch (NullPointerException e) {
logger.error("Null Pointer Exception in fetching the Identifier Type of Property: " +
property.getIdShort());
}
Endpoint ep =property.getEndpoint();
ProtocolKind protocol = ep.getProtocol();
if (property.getEndpoint()!=null ) {
switch (protocol) {
case OPCUA :
generateOpcuaGetPropertyValue( property);
break;
case CoAP:
break;
case HTTP:
break;
case MQTT:
break;
case OTHER:
break;
case ROS:
break;
default:
break;
}
}
// if (property.getEndpoint()!=null
// && property.getNameSpaceIndex() != 0
// && property.getIdentifier()!=null) {
//
// if (identifierClass.getSimpleName().equals("String")) {
//
// defaultValue =
// "(" + convertBaSyxToJavaTypes(property.getValueType()) + ") "
// + "this.connectedDevices."
// + property.getEndpoint().getName()
// + ".readValue(new NodeId(" + property.getNameSpaceIndex()
// + ", " + "\"" + property.getIdentifier() + "\"" + "))";
//
// }
//
// else {
//
// defaultValue =
// "(" + convertBaSyxToJavaTypes(property.getValueType()) + ") "
// + "this.connectedDevices."
// + property.getEndpoint().getName()
// + ".readValue(new NodeId(" + property.getNameSpaceIndex()
// + ", " + property.getIdentifier() + "))";
//
// }
//
// }
else if (property.getValue()!=null) {
if (property.getValueType().equals("String")){
defaultValue = "\"" + property.getValue() + "\"";
}
else {
defaultValue = property.getValue();
}
}
else {
;
}
String defaultGetterContent =
" " + convertBaSyxToJavaTypes(property.getValueType()) + " defaultVar = "
+ defaultValue + ";\n"
+ " return defaultVar;\n";
getterCode +=
" public " + convertBaSyxToJavaTypes(property.getValueType()) + " get_" + immediateParent + "_" + property.getIdShort() + "() {\r\n"
+ " \r\n"
+ " // Work with your Dynamic Property here. \r\n"
+ defaultGetterContent
+ " }\r\n"
+ "\r\n";
}
// ---- Setter Code Section -----
String setterCode = ""; // Not set Yet. Ask Moritz.
String finalCode = InstructionSet
+ getterCode
+ setterCode;
return finalCode;
}
private String generateOpcuaGetPropertyValue(Property property) {
// Dynamic Property default value;
String defaultValue = "null";
Class<? extends Object> identifierClass = null;
// Determining the class of the NodeId Identifier:
try {
identifierClass = property.getIdentifier().getClass();
}
catch (NullPointerException e) {
logger.error("Null Pointer Exception in fetching the Identifier Type of Property: " +
property.getIdShort());
}
if (property.getEndpoint()!=null
&& property.getNameSpaceIndex() != 0
&& property.getIdentifier()!=null) {
if (identifierClass.getSimpleName().equals("String")) {
defaultValue =
"(" + convertBaSyxToJavaTypes(property.getValueType()) + ") "
+ "this.connectedDevices."
+ property.getEndpoint().getName()
+ ".readValue(new NodeId(" + property.getNameSpaceIndex()
+ ", " + "\"" + property.getIdentifier() + "\"" + "))";
}
else {
defaultValue =
"(" + convertBaSyxToJavaTypes(property.getValueType()) + ") "
+ "this.connectedDevices."
+ property.getEndpoint().getName()
+ ".readValue(new NodeId(" + property.getNameSpaceIndex()
+ ", " + property.getIdentifier() + "))";
}
}
return defaultValue;
}
/**
* Generates the methods for those SubmodelElementCollections defined by user as "Dynamic".
* The output of these methods are then passed on to a {@code setGetHandler()} function
* in a {@code ValueDelegates} class written specifically for this Project.
*
* @param sec
* @return SubmodelElementCollection's methods generated as String.
*/
private String generateSECWS(SubModelElementCollection sec) {
String immediateParent = "";
// Checking for Parents.
if (sec.getParentSEC()!=null) {
immediateParent = sec.getParentSEC().getIdShort();
}
else if (sec.getParentSub()!=null) {
immediateParent = sec.getParentSub().getIdShort();
}
else {
logger.error("SubModelElementCollection: " + sec.getIdShort()
+ "has no Parent defined");
}
List<SubModelElementCollection> subSECS = sec.getSubModelElementCollections();
List<Property> subProps = sec.getProperties();
List<File> subFiles = sec.getFiles();
String getterCode = "";
String setterCode = "";
String finalCode = "";
String defaultGetterContent = "";
String InstructionSet = ""
+ " /**\n"
+ " * [Note:] This method body is made available in order to define a behaviour to the SubModelElementCollection \r\n"
+ " * under consideration, since this SubModelElementCollection is declared as dynamic. Meaning its value is subjected \r\n"
+ " * to certain dynamism while being updated. \r\n"
+ " * This means, one could even as well consider declaring of new SubModelElements inside the SubModelElementCollection \r\n"
+ " * method here under, if that is necessary, which will be automatically registered inside the respective submodel. \r\n"
+ " * Please under no circumstances change the method signature - i.e., Return Type, \r\n"
+ " * Method Name, Method Parameters and the Return Statement. \r\n"
+ " * All behaviours may be defined inside the method body with available \r\n"
+ " * parameters and one should abide by the return type for the method while \r\n"
+ " * computing the output. \r\n"
+ " * \r\n"
+ " * \n"
+ " */\n";
/**
* If SubModelElementCollection is Dynamic, only its ValueDelegates getter
* method is generated in the DynamicElementsWorkspace.
* The getter methods of other SubmodelElements of the SubModelElementCollection
* are not generated in that case.
*
* However, if the SubModelElementCollection is Non-Dynamic, its ValueDelegates
* getter method is not generated in the DynamicElementWorkspace.
* The getter methods of other SubModelElements of the SubModelElementCollection
* are generated in that case.
*/
// Checking if the SubModelElementCollection is Dynamic.
if (sec.isDynamic()) {
getterCode += InstructionSet
+ " public Collection<ISubmodelElement> get_" + immediateParent + "_" + sec.getIdShort() + "() {\r\n"
+ " List<ISubmodelElement> " + sec.getIdShort().toLowerCase()
+ " = new LinkedList<>();\r\n"
+ " // Work with your Dynamic SubModelElementCollection here. \r\n"
+ " return " + sec.getIdShort().toLowerCase() + ";\r\n"
+ " }\r\n"
+ "";
setterCode += ""; // Set SetterCode here.
finalCode = getterCode + setterCode;
return finalCode;
}
else {
if (!subProps.isEmpty()) {
for (Property subProp : subProps) {
finalCode += generatePropertyWS(subProp);
}
}
if (!subFiles.isEmpty()) {
for (File subFile : subFiles) {
finalCode += generateFileWS(subFile);
}
}
if (!subSECS.isEmpty()) {
for (SubModelElementCollection subSEC : subSECS) {
finalCode += generateSECWS(subSEC);
}
}
return finalCode;
}
}
private String generateFileWS(File file) {
String immediateParent = "";
// Checking for Parents.
if (file.getParentSEC()!=null) {
immediateParent = file.getParentSEC().getIdShort();
}
else if (file.getParentSub()!=null) {
immediateParent = file.getParentSub().getIdShort();
}
else {
logger.error("File: " + file.getIdShort()
+ "has no Parent defined");
}
String getterCode = "";
String setterCode = "";
Class<? extends Object> identifierClass = null;
String InstructionSet = "";
if (file.isDynamic()) {
InstructionSet = ""
+ " /**\n"
+ " * [Note:] This method body is made available in order to define a behaviour to the File \r\n"
+ " * under consideration, since this File is declared as dynamic. Meaning its value is subjected \r\n"
+ " * to certain dynamism while being updated. \r\n"
+ " * Please under no circumstances change the method signature - i.e., Return Type, \r\n"
+ " * Method Name, Method Parameters and the Return Statement. \r\n"
+ " * All behaviours may be defined inside the method body with available \r\n"
+ " * parameters and one should abide by the return type for the method while \r\n"
+ " * computing the output. \r\n"
+ " * \r\n"
+ " * \n"
+ " */\n";
// ----- Getter Code Section ------
// Dynamic Property default value;
String defaultValue = "null";
// Determining the class of the NodeId Identifier:
try {
identifierClass = file.getIdentifier().getClass();
}
catch (NullPointerException e) {
logger.error("Null Pointer Exception in fetching the Identifier Type of File: " +
file.getIdShort());
}
if (file.getEndpoint()!=null
&& file.getNameSpaceIndex() != 0
&& file.getIdentifier()!=null) {
if (identifierClass.getSimpleName().equals("String")) {
defaultValue =
"(String) "
+ "this.connectedDevices."
+ file.getEndpoint().getName()
+ ".readValue(new NodeId(" + file.getNameSpaceIndex()
+ ", " + "\"" + file.getIdentifier() + "\"" + "))";
}
else {
defaultValue =
"(String) "
+ "this.connectedDevices."
+ file.getEndpoint().getName()
+ ".readValue(new NodeId(" + file.getNameSpaceIndex()
+ ", " + file.getIdentifier() + "))";
}
}
else if (file.getValue()!=null) {
defaultValue = "\"" + file.getValue() + "\"";
}
else {
;
}
String defaultGetterContent =
" " + "String" + " defaultVar = "
+ defaultValue + ";\n"
+ " return defaultVar;\n";
getterCode +=
" public " + "String" + " get_" + immediateParent + "_" + file.getIdShort() + "() {\r\n"
+ " \r\n"
+ " // Work with your Dynamic Property here. \r\n"
+ defaultGetterContent
+ " }\r\n"
+ "\r\n";
}
String finalCode = InstructionSet
+ getterCode
+ setterCode;
return finalCode;
}
/**
* Retrieves all the Operations from the SubmodelElementCollection passed in
* that is defined in a submodel.
*
* @param sec SubModelElementCollection from which all underlying Operations
* are to be retrieved.
*
* @return Returns a List of Operations.
*/
private List<Operation> retrieveOperations(SubModelElementCollection sec) {
List<Operation> retrievedOps = new ArrayList<Operation>();
if (sec.getOperations()!=null) {
for (Operation operation : sec.getOperations()) {
retrievedOps.add(operation);
}
}
if (sec.getSubModelElementCollections()!=null) {
for (SubModelElementCollection collection : sec.getSubModelElementCollections()) {
List<Operation> ops = retrieveOperations(collection);
for (Operation op : ops) {
retrievedOps.add(op);
}
}
}
return retrievedOps;
}
/**
* Converts the BaSyx ValueTypes to Java Types.
*
* @param String A String of the BaSyx Types.
* @return A String containing the corresponding Java Type
* of the incoming BaSyx Type.
*/
private String convertBaSyxToJavaTypes(String valueType) {
switch(valueType) {
case "Int8":
return "Byte";
case "Int16": case "UInt8":
return "Short";
case "Int32": case "UInt16": case "Integer":
return "Integer";
case "NonNegativeInteger": case "NonPositiveInteger":
case "PositiveInteger": case "NegativeInteger":
return "BigInteger";
case "Int64": case "UInt32":
return "Long";
case "UInt64":
return "BigInteger";
case "Double":
return "Double";
case "Float":
return "Float";
case "Boolean":
return "Boolean";
case "String":
return "String";
case "Duration": case "DayTimeDuration":
return "Duration";
case "YearMonthDuration":
return "Duration";
case "QName":
return "QName";
case "NOTATION":
return "QName";
case "AnyURI":
return "String";
case "LangString":
return "LangString";
case "Base64Binary": case "HexBinary":
return "Byte[]";
case "GDay": case "GMonth": case "DateTime": case "GYearMonth": case "GYear": case "GMonthDay":
return "XMLGregorianCalendar";
case "None": case "DateTimeStamp":
case "AnyType": case "AnySimpleType": case "ID": case "IDREF": case "ENTITY":
return "String";
default:
return "Object";
}
}
}