Resource Adaptation

From Developer Documents
Jump to navigation Jump to search

Problem Statement

No language can define itself. There have to be extra-language mechanisms for defining basic concepts of the language. For computers to understand the language, this means some code. Here is a basic example:

public class ShapeFactory {
    public static Shape create(Graph g, Resource r) {
        ShapeResources sr = ShapeResource.getInstance(g);
        if(g.isInstanceOf(r, sr.Rectangle)) {
            ...
            return new Rectangle2D.Double(...);
        }
        else if(g.isInstanceOf(r, sr.Ellipse)) {
            ...
            return new Ellipse2D.Double(...);
        }
        ...

        throw new IllegalArgumentException("Resource is not a shape or the shape is unsupported.");
    }
}

This works as long as there is only a fixed set of shapes. Addition of every new shape needs modification to the code. We would like to have something like:

  • A declaration telling that instances of Shape can be adapted to objects of Shape class.
  • For each concrete subtype of Shape, a code creating an object of the corresponding Shape class.
  • Declarations connecting subtypes and code snippets.
  • Utility that implements the creation pattern.

Solution

Declarations are written into adapters.xml-file that has to be located in the plugin root:

<?xml version="1.0" encoding="UTF-8"?>
<adapters>

    <target interface="org.simantics.form.model.IWidgetModel">
        <baseType uri = "http://www.simantics.org/Form-1.0/Widget"/>
        <type uri = "http://www.simantics.org/Form-1.0/Label"
              class = "org.simantics.form.model.adapters.LabelAdapter"/>
        <type uri = "http://www.simantics.org/Form-1.0/InputText"
              class = "org.simantics.form.model.adapters.InputTextAdapter"/>
        ...
    </target>

    <target interface="org.simantics.form.model.ILayoutModel">
        <baseType uri = "http://www.simantics.org/Form-1.0/Layout"/>

        <!--
            Direct adaption to the specified class via
            constructor and possible arguments
        -->
        <type uri = "http://www.simantics.org/Form-1.0/GridLayout"
              class = "org.simantics.form.model.adapters.GridLayout">
            <graph/>
            <this/>
        </type>

        <!--
            A procedural adapter definition. adapterClass implements
            org.simantics.db.adaption.Adapter to construct an instance
            of the target interface.
        -->
        <adapter uri="http://www.simantics.org/Form-1.0/FormLayout"
              adapterClass="org.simantics.form.model.adapters.FormLayoutAdapter" />
    </target>

</adapters>

Each <target>...</target> section defines adaption to given interface.

<baseType> gives one or more types where all classes implementing the adaption have been inherited from.

<type> defines direct adaptation from instances of given type (uri) by construction the specified class (class) through Java Reflection API's using static method (constructor) if specified and a constructor of the class otherwise whereas <adapter> defines adaptation from instances of given type (uri) by using the given adapter (adapterClass). The adapter has to implement the following interface:

public interface Adapter<T> {
    void adapt(AsyncReadGraph g, Resource r, AsyncProcedure<T> procedure);
}

In addition the adapter may contain this kind of definitions:

      <resource uri="http://www.simantics.org/Layer0-1.0/OrderedSetElements"
          class="org.simantics.layer0.utils.binaryPredicates.OrderedSetElementsPredicate"/>

This means that the resource defined by the uri is always adapted to an instance of the given class.

The main difference between <type>/<resource> definitions and <adapter> definitions is that <type>/<resource> adaptation cannot be used for complex adaptation since both can only be used to adapt to instances of the defined adapter class and nothing else. <adapter> on the other hand can perform more complex analysis and return any instance of the adaptation target interface.

Defined adapters can be used with the following synchronous methods, where clazz-parameter refers to the interface in the adapter declaration:

public interface ReadGraph {
    ....
    <T> T adapt(Resource resource, Class<T> clazz)
            throws AdaptionException, ValidationException, ServiceException;

    <T> T adaptUnique(Resource resource, Class<T> clazz)
            throws AdaptionException, ValidationException, ServiceException;

    <T> T getPossibleAdapter(Resource resource, Class<T> clazz)
            throws ValidationException, ServiceException;

    <T> T getPossibleUniqueAdapter(Resource resource, Class<T> clazz)
            throws ValidationException, ServiceException;
    ...
}

For details, see ReadGraph interface.

In addition to <adapter>-elements, the file may contain <installer>-elements, with class-attribute referring to an implementation of the interface:

public interface AdapterInstaller {
    void install(ReadGraph g, AdaptionService service) throws Exception;
}

where

public interface AdaptionService {
    <T> void adapt(AsyncReadGraph g,
                   Resource r, Class<T> clazz, boolean possible,
                   AsyncProcedure<T> procedure);

    <T> void adaptNew(AsyncReadGraph g,
                      Resource r, Class<T> clazz, boolean possible,
                      AsyncProcedure<T> procedure);

    <T> void declareAdapter(Resource type, Class<T> clazz);

    <T> void addAdapter(Resource type, Class<T> clazz, Adapter<T> adapter);

    <T> void addInstanceAdapter(Resource resource, Class<T> clazz, Adapter<T> adapter);    
}

The class installs adapters by using method addAdapter (and addInstanceAdapter). You should use the xml-file to define your adapters if possible. The installer-mechanism is provided for cases where you want to install an adapter that is constructed with special parameters.

Construction-time Parameters

Certain parameters can be specified for adapters defined with <type> and <resource> elements. These arguments will be directly forwarded to a constructor matching this argument list in your adapter implementation class.

<this/>

The resource that is being adapted.

<graph/>

ReadGraph for reading the graph database during construction of the adapted class instance.

<bundle/>

Bundle class instance which identifies the bundle in which this adapter is defined. Can also be used to specify the referenced bundle explicitly using its symbolic name, e.g. <bundle>bundle.id</bundle>.

<string>FOO</string>

Any string constant contained as text within the <string> tag. Passed to the constructor as a java.lang.String instance.

<single/>

Requires attribute uri which must specify a URI to a relation resource. If adaption is performed for resource S, this argument element looks for a single object O from the statement (S, R, O) and passes O as an argument to the adapter class. If no single object O exists, the adapter is given null.

<related/>

Requires attribute uri which must specify a URI to a relation resource. If adaption is performed for resource S, this argument element looks for all objects O existing in the database (S, R, O) and the objects to the adapter as a Collection<Resource>.

<orderedSet/>

Requires attribute uri which must specify a URI to an ordered set resource. Looks for the specified URI in the database, assumes it is an ordered set resource and loads the whole ordered set into a Collection<Resource> instance. This collection is passed as an argument to the adapter class.

See Also

XML schema for adapter definition XML files