Skip to content
/ XsdAsm Public

Generates a Java DSL based on a XSD DSL description.

License

Notifications You must be signed in to change notification settings

xmlet/XsdAsm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Maven Central Build Coverage Vulnerabilities Bugs

XsdAsm

XsdAsm is a library dedicated to generate a fluent java DSL based on a XSD file. It uses XsdParser library to parse the XSD file into a list of Java classes that XsdAsm will use in order to obtain the information needed to generate the correspondent classes. In order to generate classes this library uses the ASM library, which is a library that provides a Java interface to perform bytecode manipulation, which allows for the creation of classes, methods, etc.

This library main objective is to generate a fluent Java DSL based on an existing XSD DSL. It aims to verify the largest number of the restrictions defined in the XSD DSL. It uses the Java compiler to perform most validations and in some cases where such isn't possible it performs run time validations, throwing exceptions if the rules of the used language are violated.

Installation

First, in order to include it to your Maven project, simply add this dependency:

<dependency>
    <groupId>com.github.xmlet</groupId>
    <artifactId>xsdAsm</artifactId>
    <version>1.0.14</version>
</dependency>

How does XsdAsm works?

The XSD language uses two main types of values: elements and attributes. Elements are complex value types, they can have attributes and contain other elements. Attributes are defined by a type and a value, which can have restrictions. With that in mind XsdAsm created a common set of classes that supports every generated DSL as shown below:


The Attribute and Element interfaces serve as a base to all attribute and element classes that will be generated in any generated DSL, with AbstractElement as an abstract class from which the concrete element classes will derive. This abstract class contains a list of attributes and elements present in the concrete element and other shared features from elements. BaseAttribute serves as a base to every Attribute that validates their restrictions. In the diagram the Html and AttrManifestString classes are shown as concrete implementations of AbstractElement and BaseAttribute, respectively.

Concrete Usage

XsdAsm provides a XsdAsmMain class that receives two arguments, the first one being the XSD file path and the second one is the name of the DSL to be generated. All the generated DSLs are placed in the same base package, org.xmlet, the difference being the chosen DSL name, for example, if the DSL name is htmlapi, the resulting package name is org.xmlet.htmlapi.

public class Example{
    void generateApi(String filePath, String apiName){
        XsdAsmMain.main(new String[] {filePath, apiName} );    
    }
}
The generated classes will be written in the target folder of the invoking project. For example, the HtmlApi project invokes the XsdAsmMain, generating all the HmlApi classes and writing them in the HtmlApi target folder, this way when HtmlApi is used as a dependency those classes appear as normal classes as if they were manually created.

Examples

Using the Html element from the HTML5 specification a simple example will be presented, which can be extrapolated to other elements. Some simplification will be made in this example for easier understanding.

<xs:element name="html">
    <xs:complexType>
        <xs:choice>
            <xs:element ref="body"/>
            <xs:element ref="head"/>
        </xs:choice>
        <xs:attributeGroup ref="commonAttributeGroup" />
        <xs:attribute name="manifest" type="xsd:anyURI" />
    </xs:complexType>
</xs:element>
With this example in mind what classes will need to be generated?

Html Class - A class that represents the Html element, represented in XSD by the xs:element name="html", deriving from AbstractElement.
body and head Methods - Both methods present in the Html class that add Body and Head instances to Html children. This methods are created due to their presence in the xs:choice XSD element.
attrManifest Method - A method present in Html class that adds an instance of the AttrManifestString attribute to the Html attribute list. This method is created because the XSD html element contains a xs:attribute name="manifest" with a xsd:anyURI type, which maps to String in Java.

public class Html extends AbstractElement implements CommonAttributeGroup {
    public Html() { }
    
    public Html attrManifest(String attrManifest) {
        this.addAttr(new AttrManifestString(attrManifest));
    }
    
    public Body body() {
        this.addChild(new Body());
    }
        
    public Head head() {
        this.addChild(new Head());
    }
}
Body and Head classes - Classes for both Body and Head elements, created based on their respective XSD xsd:element.

public class Body extends AbstractElement {
    // Contents based on the respective xsd:element name="body"
}
public class Head extends AbstractElement {
    // Contents based on the respective xsd:element name="head"
}
AttrManifestString Attribute - A class that represents the Manifest attribute, deriving from BaseAttribute. Its type is String because the XSD type xsd:anyURI maps to the type String in Java.

public class AttrManifestString extends BaseAttribute<String> {
   public AttrManifestString(String attrValue) {
      super(attrValue);
   }
}
CommonAttributeGroup Interface - An interface with default methods that add the group attributes to the element which implements this interface.

public interface CommonAttributeGroup extends Element {

   //Assuming CommonAttribute is an attribute group with a single 
   //attribute named SomeAttribute with the type String.
   default Html attrSomeAttribute(String attributeValue) {
      this.addAttr(new SomeAttribute(attributeValue));
      return this;
   }
}

Type Arguments

As we've stated previously, the DSLs generated by this project aim to guarantee the validation of the set of rules associated with the language. To achieve this we heavily rely on Java types, as shown above, i.e. the Html class can only contain Body and Head instances as children and attributes such as AttrManifest or any attribute belonging to CommonAttributeGroup. This solves our problem, but since we are using a fluent approach to the generated DSLs another important aspect is to always mantain type information. To guarantee this we use type parameters, also known as generics.

class Example{
    void example(){
        Html<Element> html = new Html<>();
        Body<Html<Element>> body = html.body();
        
        P<Header<Body<Html<Element>>>> p1 = body.header().p();
        P<Div<Body<Html<Element>>>> p2 = body.div().p();
        
        Header<Body<Html<Element>>> header = p1.__();
        Div<Body<Html<Element>>> div = p2.__();
    }        
}
In this example we can see how the type information is mantained. When each element is created it receives the parent type information, which allows to keep the type information even when we navigate to the parent of the current element. A good example of this are both P element instances, p1 and p2. Both share their type, but each one of them have diferent parent information, p1 is a child of an Header instance, while p2 is a child of a Div instance. When the method that navigates to the parent element is called, the __() method, each one returns its respective parent, with the correct type.

Restriction Validation

In the description of any given XSD file there are many restrictions in the way the elements are contained in each other and which attributes are allowed. Reflecting those same restrictions to the Java language we have two ways of ensure those same restrictions, either at runtime or in compile time. This library tries to validate most of the restrictions in compile time, as shown in the example above. But in some restrictions it isn't possible to validate in compile time, an example of this is the following restriction:

<xs:schema>
    <xs:element name="testElement">
        <xs:complexType>
            <xs:attribute name="intList" type="valuelist"/>
        </xs:complexType>
    </xs:element>
    
    <xs:simpleType name="valuelist">
        <xs:restriction>
            <xs:maxLength value="5"/>
            <xs:minLength value="1"/>
        </xs:restriction>
        <xs:list itemType="xsd:int"/>
    </xs:simpleType>
</xs:schema>
In this example we have an element that has an attribute called valueList. This attribute has some restrictions, it is represented by a xsd:list and its element count should be between 1 and 5. Transporting this example to the Java language it will result in the following class:

public class AttrIntList extends BaseAttribute<List<Integer>> {
   public AttrIntList(List<Integer> attrValue) {
      super(attrValue, "intList");
   }
}
But with this solution the xsd:maxLength and xsd:minLength restrictions are ignored. To solve this problem the existing restrictions of any given attribute are hardcoded in the class constructor. This will result in method calls to validation methods, which verify the attribute restrictions whenever an instance is created. If the instances fails any validation the result is an exception thrown by the validation methods.
public class AttrIntList extends BaseAttribute<List<Integer>> {
   public AttrIntList(List<Integer> attrValue) {
      super(attrValue, "intList");
      RestrictionValidator.validateMaxLength(5, attrValue);
      RestrictionValidator.validateMinLength(1, attrValue);
   }
}

Enumerations

In regard to the restrictions there is a special restriction that can be enforced at compile time, the xsd:enumeration. In order to obtain that validation at compile time the XsdAsm library generates Enum classes that contain all the values indicated in the xsd:enumeration tags. In the following example we have an attribute with three possible values: command, checkbox and radio.

<xs:attribute name="type">
    <xs:simpleType>
        <xs:restriction base="xsd:string">
            <xs:enumeration value="command" />
            <xs:enumeration value="checkbox" />
            <xs:enumeration value="radio" />
        </xs:restriction>
    </xs:simpleType>
</xs:attribute>
This results in the creation of an Enum, EnumTypeCommand, as shown below. This means that any attribute that uses this type will receive an instance of EnumTypeCommand instead of receiving a String. This guarantees at compile time that only the allowed set of values are passed to the respective attribute.

public enum EnumTypeCommand {
   COMMAND(String.valueOf("command")),
   CHECKBOX(String.valueOf("checkbox")),
   RADIO(String.valueOf("radio"))
}
public class AttrTypeEnumTypeCommand extends BaseAttribute<String> {
   public AttrTypeEnumTypeCommand(EnumTypeCommand attrValue) {
      super(attrValue.getValue());
   }
}

Visitors

This library also uses the Visitor pattern. Using this pattern allows different uses for the same DSL, given that different Visitors are implemented. Each generated DSL will have one ElementVisitor, this class is an abstract class which contains four main visit methods:

  • sharedVisit(Element element) - This method is called whenever a class generated based on a XSD xsd:element has its accept method called. By receiving the Element we have access to the element children and attributes.
  • visit(Text text) - This method is called when the accept method of the special Text element is invoked.
  • visit(Comment comment) - This method is called when the accept method of the special Comment element is invoked.
  • visit(TextFuction textFunction) - This method is called when the accept method of the special TextFunction element is invoked.


Apart from this four methods we have create specific methods for each element class created, e.g. the Html class. This introduces a greater level of control, since the concrete ElementVisitor implementation can manipulate each visit method in a different way. These specific methods invoke the sharedVisit as their default behaviour, as shown below.
public class ElementVisitor {
    // (...)
    
    default void visit(Html html) {
        this.sharedVisit(html);
    }
}

Element Binding

To support the definition of reusable templates the Element and AbstractElement classes were changed to support binders. This allows programmers to postpone the addition of information to the defined element tree. An example is shown below.

public class BinderExample{
    public void bindExample(){
        Html<Element> root = new Html<>()
            .body()
                .table()
                    .tr()
                        .th()
                            .text("Title")
                        .__()
                    .__()
                    .<List<String>>binder((elem, list) ->
                        list.forEach(tdValue ->
                            elem.tr().td().text(tdValue)
                        )
                    )
                .__()
            .__()
        .__();
    }
 }
In this example a Table instance is created, and a Title is added in the first row as a title header, i.e. th. After defining the table header of the table we can see that we invoke a binder method. This method bounds the Table instance with a function, which defines the behaviour to be performed when this instance receives the information. This way a template can be defined and reused with different values. A full example of how this works is available at the method testBinderUsage.

Code Quality

There are some tests available using the HTML5 schema and the Android layouts schema, you can give a look at that examples and tweak them in order to gain a better understanding of how the class generation works. The tests also cover most of the code, if you are interested in verifying the code quality, vulnerabilities and other various metrics, check the following link:

Sonarcloud Statistics

Final remarks

Some examples presented here are simplified in order to give a better understanding of how this library works. In order to allow a better usage for the generated API end user there are multiple improvements made using type arguments.

About

Generates a Java DSL based on a XSD DSL description.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages