Angular shitstorm: What's your opinion on the controversial plans for Angular 2.0?
Patterning it

A new approach to XML – Part 4: A template pattern to create XML documents

SvenEwald
template

In the concluding part of this series, learn how to create a document using templates, enrich projections, and extend the build in type converter.

The fourth and last part of this series shows you how to create a document using templates, how projections can be enriched by own method implementations, and how to extend the build in type converter.

We already demonstrated writing projections in the second part of this series. Now we are going deeper into writing projections and are learning that XMLBeam is not limited to writing values of elements and attributes only. Utilizing the subprojections shown in the first part it’s possible to write entire subgraphs into the DOM tree. This can be used to handle static and dynamic parts of the document separately and thereby reduce the effort of programming. Therefore we define templates read as projections, modified with Java code, and then composed to a new document. We provide an example reduced to its essentials to demonstrate the principle.

Example 1: Document template "svg_document_template.svg“
<svg>
   <!-- simple document template -->
        <g style="stroke:black; stroke-width:3;"/>
</svg>

The element “<svg>” is going to be the basis of our document. We will insert more elements in our code example, so we create more templates with reasonable defaults.

Example 1: Element template "rect_template.svg“
<rect width="100" height="100" style="fill:blue" />
Example 1: Element template "circle_template.svg“
<circle r="50" style="fill:red" />
Example 1: Element template "ellipse_template.svg“
<ellipse rx="50" ry="100" style="fill:yellow" />

Afterwards we define projection interfaces to be able to work with these templates. To share common methods, the projections Rectangle, Circle, and Ellipse inherit projection methods from Shape via regular Java type inheritance. There is no deeper meaning in defining projections as inner interfaces, this is just to save some lines in our example.

Example 1:
@XBDocURL("resource://svg_document_template.svg")
public interface SVG {

    public interface Shape {
        @XBWrite("./@x")
        Shape setX(int x);

        @XBWrite("./@y")
        Shape setY(int y);
    }

    @XBDocURL("resource://rect_template.svg")
    public interface Rect extends Shape {
        @XBWrite("./@width")
        Rect setWidth(int w);

        @XBWrite("./@heigth")
        Shape setHeight(int h);
    }

    @XBDocURL("resource://circle_template.svg")
    public interface Circle extends Shape {
        @XBWrite("./@r")
        Shape setRadius(int r);
    }

    @XBDocURL("resource://ellipse_template.svg")
    public interface Ellipse extends Shape {
        @XBWrite("./@rx")
        Shape setRadiusX(int rx);
        
        @XBWrite("./@ry")
        Shape setRadiusY(int ry);
    }

    @XBWrite("/svg/g/*")
    SVG setShapes(List<Shape> shapes);
}

The method setShapes(List<Shape>) is the key to our document composing pattern. It takes the projections of type Shape as parameter and inserts these projections as subelements of element “<g>” into the DOM tree of the projection SVG. The gimmick is hidden, because the written objects are no subprojections to elements, but projections to documents. XMLBeam supports this use case and inserts copies of the root elements into the DOM tree. The copies are made, because the ownership of the template elements changes during the writing operation. The wildcard “*” instructs XMLBeam to keep the names of the written subelements. An XPath like “/svn/g/shape” would result in renaming all subelements to “shape”.

Example 1: Document composition with templates
XBProjector projector = new XBProjector(Flags.TO_STRING_RENDERS_XML);

Rect rect = projector.io().fromURLAnnotation(Rect.class);
Circle circle = projector.io().fromURLAnnotation(Circle.class);
Ellipse ellipse = projector.io().fromURLAnnotation(Ellipse.class);

List<Shape> shapes = new LinkedList<Shape>();
shapes.add(rect.setX(10).setY(120));
shapes.add(circle.setX(60).setY(60));
shapes.add(ellipse.setX(180).setY(120));

SVG svgDoc = projector.io().fromURLAnnotation(SVG.class);
svgDoc.setShapes(shapes);

System.out.println(svgDoc.toString());

We get the following SVG document as result of our example.

Example 1: Result
<svg>
  <!-- simple document template -->
  <g style="stroke:black; stroke-width:3;">
    <rect height="100" style="fill:blue" width="100" x="10" y="120"/>
    <circle r="50" style="fill:red" x="60" y="60"/>
    <ellipse rx="50" ry="100" style="fill:yellow" x="180" y="120"/>
  </g>
</svg>

Custom method implementations in projections

As we have learned in the previous parts of this series, projections can be used just like plain old Java objects (POJOs). Thanks to predefined implementations of equals() and hashcode(), they can be used in Collections directly. Inheritance is supported, and by extending Serializeable, projections can even be serialized just like regular objects. Here we show how to add own method implementations to projections.

If XMLBeam is used with Java 8, you can just add your own default methods to the projection interface. XMLBeam, while compatible with Java 6, will still detect default method calls and forward them to the Java 8 implementation. If you are stuck to a pre Java 8 version and cannot use default methods, there still is a way to override methods or to add own implementations.

For this purpose XMLBeam utilizes a concept that is similar to mixins in other programming languages. XMLBeam certainly does not extend the features of the Java language with real mixins, but the term serves as a metaphor suiting this context. From now on it will be used for a bunch of methods which is to become added to a projection. And this is how it works: First a mixin-interface is defined:

A "mixin-interface“
public interface Validatable {
        boolean isValid();      
}

We let our projection interface inherit from the mixin interface Validatable so that the method isValid() becomes callable for clients of the projection interface.

A projection with inherited method "isValid()“
public interface Person extends Validatable {
        @XBRead("/person/@age“)
        int getAge();   
}

The definition of the mixin method is still missing. It is implemented in a separate “mixin-object” which has to be registered by the projector before creating the projection:

Registering a mixin implementation
projektor.mixins().addProjectionMixin(Person.class,new Validatable() {
        private Person me;
        @Override
        public boolean isValid() {
           return me.getAge() >= 18;
        }
});

XMLBeam delegates calls to person.isValid() to the mixin object which is registered for the corresponding projection interface Person. To let this projection become visible for the mixin implementation, the projector injects the current projection to the attribute “me” beforehand. Different mixin implementations may be registered for different projection interfaces. It’s even possible to override existing methods like “toString()” (see [2], or to use existing interfaces like Comparable as mixin interface (see [3]).

Creating a type converter

XMLBeam takes care that read XML data becomes converted into the desired return type of projection methods automatically. These conversions can be extended easily to meet your requirements.

A custom type converter
public static class HexToLongConversion extends Conversion<Long> {

        private HexToLongConversion(final Long defaultValue) {
            super(defaultValue);
        }

        @Override
        public Long convert(final String data) {
            return Long.parseLong(data, 16);
        }
    }
public interface Projection {
        @XBRead("/foo")
        long getData();
    }

In our example hex numbers in XML strings should be converted into Long values. To archive this, we implement the interface Conversion and hand our instance over to the DefaultTypeConverter as desired conversion for Long.

Using the type converter
XBProjector projector = new XBProjector();
DefaultTypeConverter converter = projector.config().getTypeConverterAs(DefaultTypeConverter.class);
converter.setConversionForType(Long.TYPE, new HexToLongConversion(0L));
Projection projection = projector.projectXMLString("<foo>CAFEBABE</foo>", Projection.class);
assertEquals(3405691582L, projection.getData());

That’s all which has to be done to override existing conversions. New conversions for own Java types are added the same way. If you already have a value class that can be instantiated with a String, there is a shortcut: XMLBeam automatically invokes String-constructors or String-Factory methods of projection method’s return types (see [4]).

This completes our series about XMLBeam. There are more tutorials and examples waiting to be explored at the project site [1].

About the author

Sven Ewald (Twitter handle:@Cfx) is the author of the library XMLBeam (@XMLBeam). He has been creating Java solutions for 15 years with no end in sight. Currently he works in the field of domain-specific languages for the automotive industry.

Links & Literature

[1] http://xmlbeam.org

[2] http://xmlbeam.org/faq.html#How_do_I_implement_the_toString_method_for_my_projection

[3] http://xmlbeam.org/t07.html

[4]http://xmlbeam.org/refcards.html#Let_a_projection_return_a_custom_type_that_can_be_created_with_a_String

 


Author
Comments
comments powered by Disqus