Transforms

A transform defines an operation on a source object which creates a target object.  Transforms operate recursively on a tree of objects allowing operations which apply to many levels in the object hierarchy.  Transforms are used throughout Jura whenever hierarchical operations are required e.g. when expanding out Patterns, when writing objects to a text file or during compilation.  Transform-related classes are in the jura.transform package and allow for new transforms to be created or existing transforms to be extended in an object-oriented fashion. 

Jura transforms can be thought of as a programmatic, object-orientated version of XSLT which has access to the full power and flexibility of the Jura language (and which are compiled and so much more efficient!).  Like XSLT, Jura transforms apply only to a tree of objects. 

Transforms can be applied where the source object and target object are the same instance, (a self-transform) in which case the object is simply modified by the transform.  

A simple example: the deep copy transform

The deep copy transform creates a copy of an object and all descendent objects in the object tree. In Jura, we can define it using a class DeepCopyTransform which implements the jura.transform.Transform interface, like so:

Class:DeepCopyTransform{ implements={Transform}
  Method:transform{
    doc={"Copies an object"}
    Par:t{type=TransformContext}
    Par:object{type=Object}
    //
    t={
      copy()
      transform(ALL)
    }
  }
}

The TransformContext object contains all contextual information about the current state of the transform and provides a range of methods for transforming objects in the object tree.  These include:

The context also provides methods for applying the transform recursively to children in the tree, such as particular named properties, elements contained in collections etc.

Applying a transform

A transform is applied using a transformer.  Jura provides a single transformer BeanTransformer which works off objects in memory, considering bean properties and collection elements to be children of an object. As this produces an object graph rather than a tree, links can be marked as references which will not be followed by the transformer.  Otherwise, the transformer will break the graph whenever it encounters an object for the second time (which may not give the desired behaviour).

The following Jura code uses a bean transformer to create a deep copy of an object:

BeanTransformer bt = new BeanTransformer{transform=DeepCopyTransform{}}
Object copiedObject = bt.transform(myObject)

Currently, transforms are not typed and the result must be cast to an object of the appropriate type.  Ideally, a transform should declare a source and target type and it would be checked to ensure that the transform is valid.  Such type-safe transforms are not currently available in Jura.

Using Patterns to simplify writing transforms 

Jura provides two patterns TemplateTransform (which transforms to a Class) and Template (which transforms to a Method) that simplify the creation of XSLT-like transforms.  The deep copy transform above can be rewritten using these patterns as:

TemplateTransform:DeepCopyTransform{

  Template{
    Par:object{type=Object}
    //
    copy()
    transform(ALL)
  }
}

Templates are applied according to the type of their first parameter (like match in XSLT) and can be named (equivalent to mode in XSLT). The following more complex transform demonstrates this.  It recursively prints out all the points of visible polygons in a hierarchy of polygon objects:

TemplateTransform:PrintPoints{
  Template{
    doc={"The transform only applies to polygons.  It simply calls the 'print'
     template for the polygon."}
    Par:poly{type=Polygon}
    //
    print(SELF)
  }

  Template:print{
    doc={"Prints the polygon.  If the polygon is visible, it calls the 'print'
     template for all children of the polygon."}
    Par:poly{type=Polygon}
    //
    If{test=poly.isVisible() // print(ALL)}
  }

  Template:print{
    doc={"Prints a point."}
    Par:p{type=Point}
    //
    System.err.println("Point: "+p.x+","+p.y)
  }
}

The use of these two patterns has changed the way the Jura code for the transform looks, from Java-like source code to XSLT-like source code.  This provides an example of how patterns can be applied to modify the basic Jura language so that it is compact for a particular purpose.

Using a transform during the compilation process

If required, a transform can be inserted into the compilation process to allow, for example, debugging code to be inserted in all methods of a particular name.  Alternatively, this can be made to happen automatically by using a Pattern and performing the transformation when the pattern is resolved (the transform would then apply only to children of the pattern).