Skip to content

cobigen core_development

devonfw-core edited this page Nov 23, 2021 · 26 revisions

CobiGen Core Development

CobiGen uses Apache FreeMarker as engine for generation through FreeMarker templates.

Note

The core implementation are divided in three projects:

  • cobigen-core-api: Mainly composed by interfaces that will be called from the Eclipse plug-in.

  • cobigen-core: The implementation of the interfaces are within.

  • cobigen-core-test: As the name suggests, used for test purposes.

Extension Mechanism

The extension package from the API project contains the interfaces to be implemented if necessary by the sub plugins:

  • `GeneratorPluginActivator.java`

  • `InputReader.java`

  • `MatcherInterpreter.java`

  • Merger.java

  • `TriggerInterpreter.java`

  • `ModelBuilder.java`

The ModelBuilder is an interface for accessing the internal model builder instance. Is implemented by `ModelBuilder.java` from the model package from the implementation project that provides the methods to call the createModel() from the correspondent input reader from the correspondent trigger interpreter to create the object models for a given object.

The to package have the transfer objects of template, matcher, increment and variable assignment classes that will be used as "communication channel" between the core and sub plug-ins methods

Plugin Registry

The core must load all the sub plugins to get their Merger, Matcher, TriggerInterpreter and InputReader. That elements must implement their respective interfaces from the core.

Diagram 1

Is important to note that not all the sub plug-ins need to have implemented a Matcher and/or an InputReader (advanced information here)

Load Plugin

The process of loading plugins to the core is done at the eclipse-plugin initialization.

Each sub plugin has an activator class that extends the `GeneratorPluginActivator` interface from the extension package. That class implements the methods bindMerger() and bindTriggerInterpreter().

Diagram 2

This is the class passed as argument to the loadPlugin() method of `PluginRegister.java` of the pluginmanager package.

This method registers the mergers and the trigger interpreter of the sub plugins to the core. The trigger interpreter has the correspondent input reader of the plugin.

CobiGen Initialization

The CobiGen initialization must initialize the context configuration and the FreeMarker configuration

FreeMarker Initialization

When a CobiGen object is instantiated, the constructor initializes the Freemarker configuration creating a configuration instance from the class freemarker.template.Configuration and adjust its settings.

freeMarkerConfig = new Configuration(Configuration.VERSION_2_3_23);
freeMarkerConfig.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_23).build());
freeMarkerConfig.clearEncodingMap();
freeMarkerConfig.setDefaultEncoding("UTF-8");
freeMarkerConfig.setLocalizedLookup(false);
freeMarkerConfig.setTemplateLoader(new NioFileSystemTemplateLoader(`configFolder`));

Using the FileSystemUtil from the util package the URI of the root folder containing the context.xml and all templates, configurations etc…​ is converted to a Path object passing it as argument to the ContextConfiguration constructor. The ContextConfiguration creates a new ContextConfiguration from the config package with the contents initially loaded from the context.xml

Note

How the ContextConfiguration works explained deeply here.

The Configuration initialization requires the version of FreeMarker to be used and at the ObjectWrapper initialization as well. The DefaultObjectWrapperBuilder creates an DefaultObjectWrapper object that maps Java objects to the type-system of FreeMarker Template Language (FTL) with the given incompatibleImprovements specified by the version used as argument.

The configuration of FreeMarker requires to specify to a `TemplateLoader`. A `TemplateLoader` is an interface provided by FreeMarker library that the developer should implement to fit the needs. The `TemplateLoader` implementation at CobiGen is the class `NioFileSystemTemplateLoader.java` from the config.nio package.

Diagram 5

Context Configuration

The context configuration reads the context.xml file from the template project (default: CobiGen_Templates) passing the path as argument to the constructor. At the constructor, it is created an instance of ContextConfigurationReader.java from the config.reader package.

Note

Please, check the CobiGen configuration for extended information about the context.xml and templates.xml configuration.

That reader uses the JAXB, JAXB (Java Architecture for XML Binding) provides a fast and convenient way to bind XML schemas and Java representations, making it easy for Java developers to incorporate XML data and processing functions in Java applications. As part of this process, JAXB provides methods for unmarshalling (reading) XML instance documents into Java content trees.

JAXB

<<<<<<< HEAD JAXB auto generates the Java object within the JAXBContext specified at the xmlns attribute of the contextConfiguration field from the context.xml file

Unmarshaller unmarshaller = JAXBContext.newInstance(ContextConfiguration.class).createUnmarshaller();

That auto-generation follows the contextConfiguration.xsd schema. Each Java object follows the template specified with the field <xs:CompleType> from the schema file.

<xs:complexType name="trigger">
    <xs:sequence>
         <xs:element name="containerMatcher" type="tns:containerMatcher" minOccurs="0" maxOccurs="unbounded"/>
         <xs:element name="matcher" type="tns:matcher" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="id" use="required" type="xs:NCName"/>
    <xs:attribute name="type" use="required" type="xs:string"/>
    <xs:attribute name="templateFolder" use="required" type="xs:string"/>
    <xs:attribute name="inputCharset" use="optional" type="xs:string" default="UTF-8"/>
 </xs:complexType>
 <xs:complexType name="matcher">
    <xs:sequence>
        <xs:element name="variableAssignment" type="tns:variableAssignment" minOccurs="0" maxOccurs="unbounded"/>
    </xs:sequence>
    <xs:attribute name="type" type="xs:string" use="required"/>
    <xs:attribute name="value" type="xs:string" use="required"/>
    <xs:attribute name="accumulationType" type="tns:accumulationType" use="optional" default="OR"/>
  </xs:complexType>

JAXB

The generated Java objects has the elements and attributes specified at the schema:

@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "trigger", namespace = "http://capgemini.com/devonfw/cobigen/ContextConfiguration", propOrder = {
    "containerMatcher",
    "matcher"
})
public class Trigger {
    @XmlElement(namespace = "http://capgemini.com/devonfw/cobigen/ContextConfiguration")
    protected List<ContainerMatcher> containerMatcher;
    @XmlElement(namespace = "http://capgemini.com/devonfw/cobigen/ContextConfiguration")
    protected List<Matcher> matcher;
    @XmlAttribute(name = "id", required = true)
    @XmlJavaTypeAdapter(CollapsedStringAdapter.class)
    @XmlSchemaType(name = "NCName")
    protected String id;
    @XmlAttribute(name = "type", required = true)
    protected String type;
    @XmlAttribute(name = "templateFolder", required = true)
    protected String templateFolder;
    @XmlAttribute(name = "inputCharset")
    protected String inputCharset;
    ...
    ..
    .
}

This process it is done when calling the unmarshal() method.

Object rootNode = unmarshaller.unmarshal(Files.newInputStream(contextFile));
Note

For extended information about JAXB check the offical documentation.

Version Validation

If the version retrieved after the unmarshal process is null, an InvalidConfigurationException defined at exceptions package will be thrown.

If it is not null, will be compared using the validate() method from `VersionValidator.java` from config.versioning package with the project version retrieved by the `MavenMetadata.java`. The `MavenMetadata.java` file is provided by the POM while building the JAR file

<build>
    <plugins>
      <!-- Inject Maven Properties in java-templates source folder -->
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>templating-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-version-class</id>
            <goals>
              <goal>filter-sources</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      ...
      ..
      .
    </plugins>
</build>

MavenMetadata gets the current CobiGen version by reading the <version> label inside the <project> label from the POM file

public class MavenMetadata {
    /** Maven version */
    public static final String VERSION = "${project.version}";
}
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <modelVersion>4.0.0</modelVersion>
  <artifactId>cobigen-core</artifactId>
  <name>CobiGen</name>
  <version>2.2.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  ...
  ..
  .
}

The comparison has three possibilities:

  1. Versions are equal → Valid <<<<<<< HEAD

  2. context.xml version is greater than current CobiGen version → InvalidConfigurationException

  3. Current CobiGen version is greater that context.xml version → Compatible if there not exists a version step (breaking change) in between, otherwise, throw an error.

Reaching this point, the configuration version and root node has been validated. Unmarshal with schema checks for checking the correctness and give the user more hints to correct his failures.

SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
ContextConfigurationVersion latestConfigurationVersion = ContextConfigurationVersion.getLatest();
try (
    InputStream schemaStream = getClass().getResourceAsStream("/schema/" + latestConfigurationVersion
                                                              + "/contextConfiguration.xsd");
    InputStream configInputStream = Files.newInputStream(contextFile)) {
    Schema schema = schemaFactory.newSchema(new StreamSource(schemaStream));
    unmarshaller.setSchema(schema);
    rootNode = unmarshaller.unmarshal(configInputStream);
    contextNode = (ContextConfiguration) rootNode;
}

Load Triggers, Matchers, container Matcher, Accumulation Types and Variable Assignments

To finish the context configuration initialization, the, trigger, matchers, container matchers, accumulation types and variables assignments are retrieved from the correspondent Java objects generated by JAXB.

public Map<String, Trigger> loadTriggers()
private List<Matcher> loadMatchers(Trigger trigger)
private List<ContainerMatcher> loadContainerMatchers(Trigger trigger)
private List<VariableAssignment> loadVariableAssignments(Matcher matcher)

Perform Generation

Depending on the input, the generation process can begin from two different generate() methods called at the CobiGenWrapper from the eclipse-plugin:

public void generate(TemplateTo template, boolean forceOverride) throws IOException, TemplateException, MergeException {
    if (singleNonContainerInput) {
        Map<String, Object> model = cobiGen.getModelBuilder(inputs.get(0), template.getTriggerId()).createModel();
        adaptModel(model);
        cobiGen.generate(inputs.get(0), template, model, forceOverride);
    } else {
        for (Object input : inputs) {
            cobiGen.generate(input, template, forceOverride);
        }
    }
}

Single Non Container Input

If the input is a single non container input, first step is to create the model, then allow customization by the user (adaptModel()) and finally call the generate() method from CobiGen using the input, template, model and the boolean forceOverride.

The generation process in this case will follow this main steps:

  1. Check if the input is not null

  2. Get the trigger interpreter for the type of the trigger of the template

  3. Set the root folder for the templates to use for the generation

  4. Get the input reader for the trigger interpreter retrieved

  5. Test if the input is a package.
    This only can be possible in the case of java inputs. As the input is a single non container input, this check will fail and the execution will continue.

  6. Check if the model parameter is null and if it is, create a new model
    As the model has been created at the CobiGenWrapper, there is no need to create it again.

  7. Get the destination file.

  8. Check if the destination file already exists
    If it exists, but the forceOverride is set to true or the merge strategy of the template is null, the file will be overwritten, not merged. Otherwise, first generate output into a writter object, get the merger and merge the original file with the writter and write the file with the merge result.

  9. If the file does not exist, simple write the file.

Single Container Input or multiple files selection

The other case is, or the input is multiple files selection, the generation process will be performed for each individual file of the selection, but the model will be created at the step 6 of the steps of the Single Non Container Input and not allowing the user customization.

Clone this wiki locally