Rule based code generation

DescriptionThis document describes rule-based approach to code generation implemented in Argun.
Id177
Last modifiedMon Jan 21 22:05:27 EST 2008
GUID37c63156c4d8c2b96d27276693325f5c697d018b
Glossary Hammurapi rules glossary

Introduction

 This article describes code generation facilities in Argun.

Concepts

  • Model – an object graph providing information for code generation. Model doesn't have to be visual and doesn't have to be UML. Examples of models:
    • Database metadata
    • Java class structure
  • Model is defined in Language. For the aforementioned model examples languages are JDBC metadata and reflection API's respectively.
  • Transformation converts model in one language into a model in another. In Argun transformation is a collection of rules and templates.

Code generation process can be described using the formula below

ML1*TL1,L2 => ML2

whic means that a transformation between languages one and two is applied to a model defined in language one. Transformation process yields a model defined in language two (which can be the same as language one). For example, transformation appied to database metadata model results in generation of data access classes defined in Java language.

 
Figure 1. Code generation process
.

Figure 1 depicts rule-based code generation process implemented in Argun.

  • First of all source model is constructed, perhaps from multiple sources. For example, model for generation data access layer can be constructed from database metadata obtained through JDBC metadata API and SQL statements defined in XML file.
  • Then the model is traversed by a visitor. The visitor passes model elements to a rule set.
  • In the rule set model elements are passes to rules. Each rule works on a particular type of model elements. Code generation rules typically extend biz.hammurapi.web.mda.MdaRule.
  • Rules use use templates and evaluators to generate fragments of code which is output to channels.
  • Channels form a hierachy. As a river is formed from multiple creeks and streams, one file can be constructed from outputs to multiple channels. See JavaDoc for biz.hammurapi.web.mda package for more details.

Several notes:

Building software
is a process
of binding decisions
to make them
executable
 

  • Transformation is usually a pattern instantiation - eliminates unnecessary creativity.
  • Traditional development – sequential (addition). E.g. for each table in database a data access class shall be created. All classes are very similar in structure.
  • Code generation – orthogonal cross-cutting (multiplication). E.g. a data access class template is created once and then applied to all tables in the database.ksdkfsld
  • Custom models – don’t have to be as complex as UML/XMI/MOF.

Transformations and templates tend to become very complex. Rule-based approach to transformation allows to break the process into small steps. Templates can be written in multiple languages, which allows to choose the right language for a particular template depending on source and target languages.

Tutorial

Installation

  • Download and unzip code-generation-tutorial.zip.
  • Download argun.zip. Extract files and then extract files from argun.war. Copy jar files from WEB-INF/lib folder to the lib folder of code generation tutorial.
  • Download hammurapi-rules-2.11.1.1.zip. Extract files and copy files from the lib folder to the lib folder of code generation tutorial.
  • Download hammurapi-rules-tutorial-2.5.2.1.zip. Extract files. Copy files from the lib folder to the lib folder of code generation tutorial. Copy familyties.xml to the root folder of code generation tutorial.
  • Execute cgtutorial.bat or cgtutorial.sh to generate family ties report.

Bits and pieces

<ruleset type="biz.hammurapi.config.ElementNameDomConfigurableContainer">
   
    <name>Family ties report generator</name>
    <description>Generates HTML report about inferred famility ties</description>
   
    <!-- <knowledge-compactor type="biz.hammurapi.rules.MaximizingKnowledgeCompactor"/> -->
   
    <handle-manager type="biz.hammurapi.rules.KnowledgeMaximizingHandleManager"/>
   
    <collection-manager type="biz.hammurapi.rules.PojoCollectionManager">
        <collectionType>biz.hammurapi.rules.KnowledgeMaximizingSet</collectionType>
    </collection-manager>
   
    <rules type="biz.hammurapi.rules.QueueingRulesContainer">
        <rule type="biz.hammurapi.tutorials.codegeneration.IndexGenerator">
            <name>Index generator</name>
            <description>Generates index.html</description>
        </rule>
       
        <rule type="biz.hammurapi.tutorials.codegeneration.PersonGenerator">
            <name>Person generator</name>
            <description>Generates person pages.</description>
        </rule>
       
        <rule type="biz.hammurapi.tutorials.codegeneration.RelationshipGenerator">
            <name>Relationship generator</name>
            <description>Generates relationship entries on person pages.</description>
        </rule>
       
    </rules>
</ruleset>

codegen.xml defines ruleset  

<TR>
    <TD><a href="<%=person.getName()%>.html"><%=person.getName()%></a></TD>
</TR>

Template for generating index.html file defined in JXP language. 

<TR>
    <TD><a href="${name}.html">${name}</a></TD>
    <TD>${type}</TD>
</TR>

Template for generating rows in the table of relatives is defined in token expansion language.

package biz.hammurapi.tutorials.codegeneration;

import java.io.File;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.rules.RuleRuntime;
import javax.rules.RuleServiceProvider;
import javax.rules.RuleServiceProviderManager;
import javax.rules.StatefulRuleSession;

import biz.hammurapi.util.CollectionVisitable;
import biz.hammurapi.util.Visitor;
import biz.hammurapi.web.eval.BeanShellEvaluator;
import biz.hammurapi.web.eval.FileResourceLocator;
import biz.hammurapi.web.eval.JxpEvaluator;
import biz.hammurapi.web.eval.SimpleEvaluatorFactory;
import biz.hammurapi.web.eval.TokenEvaluator;
import biz.hammurapi.web.eval.VelocityEvaluator;
import biz.hammurapi.web.eval.XsltEvaluator;
import biz.hammurapi.web.mda.FileRootChannel;

public class Tutorial {


    /**
     * Main method for the tutorial.
     * @param args The first argument is rule set URI.
     */
    public static void main(String[] args) throws Exception {
        System.out.println("Code generation tutorial");
       
        if (args.length!=2) {
            System.out.println("Usage: java <options> biz.hammurapi.rules.tutorial.Tutorial <family ties rule set url> <generator rule set url>");
            System.exit(1);
        }
               
        List objects = biz.hammurapi.rules.tutorial.Tutorial.infer(args[0], null);
       
        Collections.sort(
                objects,
                new Comparator() {

                    public int compare(Object o1, Object o2) {
                        return o1.toString().compareTo(o2.toString());
                    }
                   
                });
       
        String ruleServiceProviderClassName = "biz.hammurapi.rules.jsr94.FileRuleServiceProvider";
        Class.forName(ruleServiceProviderClassName);
        RuleServiceProvider serviceProvider = RuleServiceProviderManager.getRuleServiceProvider(ruleServiceProviderClassName);
        RuleRuntime runtime = serviceProvider.getRuleRuntime();
        String ruleSetUrl = args[1];
        System.out.println("Loading rule set from "+ruleSetUrl);
        Map context = new HashMap();
       
        SimpleEvaluatorFactory evaluatorFactory = new SimpleEvaluatorFactory();
        context.put("evaluator-factory", evaluatorFactory);
       
        evaluatorFactory.register("Java", new BeanShellEvaluator(), ".java");
        evaluatorFactory.register("Jxp", new JxpEvaluator(), ".jxp");
        evaluatorFactory.register("Token", new TokenEvaluator(), ".txt");
        evaluatorFactory.register("Velocity", new VelocityEvaluator(), ".vm");
        evaluatorFactory.register("XSLT", new XsltEvaluator(), ".xsl");
               
        FileResourceLocator resourceLocator = new FileResourceLocator(new File("templates"));
        context.put("resource-locator", resourceLocator);
       
        FileRootChannel rootChannel = new FileRootChannel(new File("report"));
        context.put("root-channel", rootChannel);
       
        final StatefulRuleSession session = (StatefulRuleSession) runtime.createRuleSession(ruleSetUrl, context, RuleRuntime.STATEFUL_SESSION_TYPE);
               
        // Adding objects to the session. Using CollectionVisitable instead of simple iteration to demonstrate the concept.       
        new CollectionVisitable(objects, false).accept(
                new Visitor() {

                    public boolean visit(Object target) {
                        try {
                            session.addObject(target);
                            return true;
                        } catch (Exception e) {
                            e.printStackTrace();
                            return false;
                        }
                    }
                   
                }
        );
       
        session.executeRules();       
        session.release();
       
        rootChannel.flush();
    }

}
 

This is the main class of the tutorial. It uses Hammurapi Rules tutorial to infer family relationships. Then these relationships are passed to code generation rules. These rules output code fragments to channels which form index file and a file for each person which has relatives.

package biz.hammurapi.tutorials.codegeneration;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import biz.hammurapi.rules.tutorial.conclusions.Relative;
import biz.hammurapi.rules.tutorial.facts.Person;
import biz.hammurapi.web.HammurapiWebException;
import biz.hammurapi.web.mda.MdaRule;
import biz.hammurapi.web.mda.SubChannel;

public class IndexGenerator extends MdaRule {
   
    /**
     * To avoid duplicates.
     */
    private Set persons = new HashSet();
   
    public void infer(Relative relative) throws IOException, HammurapiWebException {
        if (persons.isEmpty()) { // First invocation, set footer and header
            SubChannel indexChannel = getRootChannel().getChannel("index.html");
            indexChannel.getHeader().write("<HTML><HEAD><TITLE>People</TITLE></HEAD><BODY><H1>People</H1><TABLE border='1'><TR><TH>Name</TH></TR>");
            indexChannel.getFooter().write("</TABLE></BODY></HTML>");
        }
       
        Person person = relative.getObject();
        if (persons.add(person)) {
            Map context = new HashMap();
            context.put("person", person);
            render(context, "index", new String[] {"index.html", "table"});
        }
    }

    public void reset() {
        persons.clear();
    }
}
 

Index generator rule creates index.html file using index.jxp template. Note that the rule doesn't specify that JXP language shall be used for rendering and that index.jxp shall be used as a template. Evaluation factory takes template name, "index" in this case, and then finds appropriate template by iterating over available languages and looking for <template name>.<extension> file. In this case "index.jxp". The first found template is used for code generation. 

package biz.hammurapi.tutorials.codegeneration;

import java.io.IOException;
import java.io.Writer;
import java.util.HashSet;
import java.util.Set;

import biz.hammurapi.rules.tutorial.conclusions.Relative;
import biz.hammurapi.rules.tutorial.facts.Person;
import biz.hammurapi.web.mda.MdaRule;
import biz.hammurapi.web.mda.SubChannel;

public class PersonGenerator extends MdaRule {
   
   
    /**
     * To avoid duplicates.
     */
    private Set persons = new HashSet();
   
    public void infer(Relative relative) throws IOException {
       
        Person person = relative.getObject();
        if (persons.add(person)) {
            SubChannel personChannel = getRootChannel().getChannel(person.getName()+".html");
            Writer header = personChannel.getHeader();
            header.write("<HTML><HEAD><TITLE>");
            header.write(person.getName());
            header.write("</TITLE></HEAD><BODY><H1>");
            header.write(person.getName());
            header.write("</H1><H2>Relatives</H2><TABLE border='1'><TR><TH>Name</TH><TH>Relationship</TH></TR>");
           
            personChannel.getFooter().write("</TABLE></BODY></HTML>");
        }
    }

    public void reset() {
        persons.clear();
    }
}
 

This template generates person files with the exception of relative rows, which are generated by the next rule.

package biz.hammurapi.tutorials.codegeneration;

import java.util.HashMap;
import java.util.Map;

import biz.hammurapi.rules.tutorial.conclusions.Relative;
import biz.hammurapi.web.HammurapiWebException;
import biz.hammurapi.web.mda.MdaRule;

public class RelationshipGenerator extends MdaRule {
   
    public void infer(Relative relative) throws HammurapiWebException {
       
        Map context = new HashMap();
        context.put("name", relative.getSubject().getName());
        String className = relative.getClass().getName();
        context.put("type", className.substring(className.lastIndexOf('.')+1));
        render(context, "relation", new String[] {relative.getObject().getName()+".html", "table"});
       
    }

References