Custom Data Classes

Custom data classes #

This page contains the following content:

Creation of a custom data class #

This page shows an example of a very simple custom data class. Here, only the implementation class is shown. Usually, an interface class (SMTestDataClass.java in this example) should be provided for variables such as the measure name (CLASS_NAME) and the JavaDoc annotations.

public class TestDataClassImpl extends DataClassImpl implements SMTestDataClass {
  
  public TestDataClassImpl() {
    super(SMTestDataClass.CLASS_NAME);
    try {
      this.setAbstract(false);
    } catch (IllegalEditException e) {
      throw new ApplicationError(Model.COMPONENT, LOG_SUBCLASS_CREATION_FAILED, this);
    }
  }

  @Override
  protected void createSystemSubClasses() {
    // Empty until subclasses are created.
  }

  @Override
  public boolean isTestDataClass() {
    return true;
  }

  @Override
  public DataObject newObject() throws IllegalInstantiationException {
    checkInstantiability();
    return new TestDataObjectImpl(this);
  }
}

Each data class inherits from the class DataClassImpl or one of its subclasses. The constructor always calls the constructor of the superclass. If the current class is an abstract class, the method setAbstract must be called with the value true, otherwise with false. Through inheritance, it is necessary to implement the methods createSystemSubClasses and newObject. If the current class has subclasses, these are created in the method createSystemSubClasses and the method createSystemClassTree is called. In this example case, the call new TestDataClassImpl().createSystemClassTree(this) would be added to the method createSystemSubClasses of DataClassImpl. This method is implemented in the abstract DataClassImpl and inserts the respective class into the class tree, then the method createSystemSubClasses for the subclasses is called. A new method can also be created in the DataClassImpl to indicate whether a class is an instance of a class. Here this would be isTestDataClass, which returns false in DataClassImpl and all other subclasses, but true in this instance, and it’s child instances. The method newObject creates an object of this data class and calls the constructor of the respective object class for it (example implementation is listed below).

In the ModelImpl class (and its interface), a specific getter method for the class can be implemented. Here it would look like this:

@Override
  public TestDataClass getTestDataClass() {
    return getClass(TestDataClass.CLASS_NAME);
  }

Creation of a custom data object #

Analogous to the implementation of the data class, an interface (here TestDataObject.java) and a corresponding implementation are created for the data object. Once again, only the implementation class is presented here.

public class TestDataObjectImpl extends DataObjectImpl implements TestDataObject {

  public TestDataObjectImpl(TestDataClass dataClass) throws IllegalInstantiationException {
    super(dataClass);
    try {
      [...]
    } catch (InvalidNativeValueException e) {
      throw new IllegalInstantiationException(e);
    }
  }
  
  @Override
  public boolean isTestDataObject() {
    return true;
  }

  @Override
  public TestDataClass getTestDataClass() {
    return (TestDataClass) getDataClass();
  }

  @Override
  public boolean hasSameValueAsIn(DataObject object) {
    [...]
  }

  @Override
  public void assertSameValueAsIn(DataObject object) throws AssertSameValueAsInException {
    [...]
  }

  @Override
  public DataObject copy() {
    TestDataObject copiedObject = new TestDataObjectImpl(this.getTestDataClass());
    // Copy the attributes here.
    [...]
    return copiedObject;
  }
}

Analogous to the data class, each data object inherits from the class DataObjectImpl or one of its subclasses. The constructor always calls the constructor of the superclass. Afterwards, any changes can be made to the object. Through inheritance, it is necessary to implement the methods hasSameValueAsIn, assertSameValueAsIn and copy. The method hasSameValueAsIn checks if another object contains the same values. It checks if it belongs to the same data class and if all attributes are identical. If so, true is returned, otherwise false. The method assertSameValueAsIn works analogously, but does not output a boolean value, but an exception if the value is not identical. The copy method creates a new object of these data classes with the identical attributes of the current object. A new method can also be created in the DataObjectImpl to indicate whether a class is an instance of a class. Here this would be isTestDataObject, which returns false in DataClassImpl and all other subclasses, but true in this instance, and it’s child instances. Furthermore, a class specific method getTestDataClass can be created, which uses the default method getDataClass and casts it at the current required class.

Usage of the data class during runtime #

No changes to the runtime environment are required to use a custom data class, because they are already created through the method calls of the superclasses. After starting ProCAKE first, the data model of this instance has to be used. For this, the following code can be used:

CakeInstance.start(PATH_COMPOSITION, PATH_MODEL, PATH_SIM_MODEL, PATH_CASEBASE);
Model model = ModelFactory.getDefaultModel();

For the method CakeInstance.start the string values for the path of composition and the casebase has to be set (usually to e.g. "/cake/composition.xml"). The paths for the data and the similarity model can be null. The data model can be retrieved using the ModelFactory.

The class can be queried and used as follows:

TestDataClass testDataClass = model.getTestDataClass();
TestDataObject testDataObject = testDataClass.newObject();

Using the getTestDataClass method created above, the instance of the data class is returned. Alternatively, the method getClass using the attribute CLASS_NAME of the data class interface can be used (model.getClass(TestDataClass.CLASS_NAME)). The method newObject creates a new object of the data object class.

This is the easiest way to use a new data class. But in this approach, an XML instantiation of a data class is not supported. If this is required, the following approach has to be used.

Adding the data class to the data model before runtime #

Before a data class can be used in the XML files, it has to be added to the XML schema cdm.xsd (which can be found in src/main/resources/cake/schema/). Here, a new element entry for the measure has to be created. The creation of this element is shown in the following:

<xs:element name="TestDataClass">
    <xs:complexType>
        <xs:complexContent>
            <!-- Attributes can be defined here according to the following scheme -->
            <xs:attribute name="exampleAttribute" type="xs:int" use="optional"/> 
            [...]
        </xs:complexContent>
    </xs:complexType>
</xs:element>

For several data classes, extensions exist, that can be used. For example, if a subclass of an Atomic class is created, the extension AtomicClass can be used. After the creation of the new element, it has to be added to the element Model. This element contains a choice of elements, where the new element can be added by using the following reference:

<xs:element ref="TestDataClass"/>

Now, the new measure can be instantiated in an XML file:

<TestMeasure name="SMTestMeasure" class="Atomic"/>

Before this XML file can be read and the data class created, the class ModelHandler has to be used. It provides a function startElement(Attributes attributes), that is used for each XML element. To identify the measures, tags from the class ModelTags are used. The tag for the new measure can be added there. In the startElement method a else if statement is added, which refers to a method, that creates the new measure.

if(localName.equals(TAG_TESTDATACLASS)) {
    startElementTestDataClass(attributes);
}

Here, TAG_TESTDATACLASS refers to a tag of ModelTags. Alternatively, a normal string can be used as input. The method startElementTestDataClass(attributes) can look like:

private void startElementTestMeasure(Attributes attributes) {

    String name = attributes.getValue(ATT_NAME);
    String superClassName = attributes.getValue(ATT_SUPERCLASS);

    TestDataClass superClass = model.getClass(superClassName);

    if (superClass == null) {
      throw new CakeSaxException(COMPONENT, LOG_SUPERCLASS_NOT_FOUND, this.getClass().getName(),
          superClassName);
    }

    this.currentClass = (TestDataClass) superClass.createSubclass(name);
}

For more complex data classes, values of the attributes can be called by the method getValue(String attributeName) of the class Attribute. An adaptation of the schema for the case bases is not necessary, there the new class can be used directly. For special classes, such as the NESTGraph, the methods can be shifted into corresponding subhandlers and subfiles.

Adding the data class to the model writer #

In order to be able to use the data class in the output of ObjectPools, adaptations in the ObjectWriter class are necessary. A special writer-method must be written, which can look like this:

private void writeTestData(
      String name, TestDataObject object, DataClass attributeType, XMLSchemaBasedWriter writer)
      throws InvalidNativeValueException, IOException {
    writer.appendElement(PREFIX_CDOL, TAG_TESTDATA);
    if (!object.getDataClass().equals(attributeType)) {
      writer.addAttribute(ATT_CLASS, object.getDataClass().getName());
    }
    writeProperties(object, writer);
    writer.appendElement(PREFIX_CDOL, TAG_TESTDATACONTENT);
    writer.addText(object.getTestDataClass().nativeToString(object.getNativeObject()));
    // Other attributes can be added here according to the same scheme.
    writer.finishElement();
    writer.finishElement();
  }

This method must be referenced in the write method. This can look like this:

if (dClass.isTestDataClass()) {
    writeTestData(((TestDataObject) object), attributeType, writer);
}

Similarly, another method writeTestDataAttribute can be created if the use of these data classes is allowed in Aggregates, for example.