WARNING

The SpoonJDT/Spoonlet plugin is no longer maintained and does not work with recent versions of Eclipse. Processors are now used in command line or in the build process.

Serbo-Croatian translation

How to install and use SpoonJDT (Eclipse Plugin)

SpoonJDT is installed like any other Eclipse plugin through the Update Manager. The Spoon Update Site is http://spoon.gforge.inria.fr/eclipse/. Once the plugin is installed, you can deploy Spoonlets that perform specific processing. For more details, go here.

How to deploy your processors in a Spoonlet for Eclipse

You can deploy a set of processors to be applied to an Eclipse Java project. To do so, you have to create a Spoonlet XML descriptor called 'spoon.xml'. This file should be placed in the jar file used to distribute your spoonlet (the path does not matter). This Spoonlet jar file can be deployed using the SpoonJDT plugin (see ). Note that if you are developping your processors in an Eclipse Java project, you do not need to package them in a jar file, since the SpoonJDT plugin can deploy a Spoonlet by referencing a 'spoon.xml' file within the current Eclipse's workspace.

The DTD of the 'spoon.xml' file is available here

Example:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE spoon SYSTEM "http://spoon.gforge.inria.fr/pub/xml/spoonlet.dtd" >

<spoon>
  <processor name="A short description"
     class="x.y.z.MyProcessor" active="true"
     doc="A documentation on what the processor is doing.">
  </processor>
</spoon>

back

Tutorial: Enhancing Java with Spoon

Have you ever thought of the Java compiler checking more than it does now? Have you ever thought of code being automatically generated or validated by the compiler instead of being written and maintained manually? Spoon is a framework that allows you to enhance the Java semantics through static analysis and generative programming. With Spoon, you can write compilation components called “Spoonlets”, which can be deployed in Eclipse using the SpoonJDT plugin. Spoonlets can provide generic validations or transformations, which are not provided from scratch by the compiler, such as the ones provided by Findbugs project (http://findbugs.sourceforge.net/). Spoonlets can also implement validations and transformations to support your favorite frameworks (SAX, Struts, Hibernate ...) in order to simplify their use and avoid common mistakes. In this tutorial, we learn how to use and create new Spoonlets, which will help not only to dramatically increase the quality of your own Java developments, but also of the developments of the programmers working with you.

Table of Contents

-

Getting Started with Spoon

Here, we show how to enhance Java with Spoon. This is done with a compilation component called a Spoonlet.

back

What is a Spoonlet?

A Spoonlet is a compilation component distributed as a packaged jar file that contains one or several program processors written in Java. These processors can validate Java programs using static analysis, or transform the program using generative programming techniques. A Spoonlet can be deployed in Eclipse and applied to an Eclipse Java project. It then acts like a plugin that extends or restricts the Java language capabilities through static analysis and/or transformations.

As examples, you can imagine a Spoonlet to:

  • force the definition of Javadoc comments on public methods and fields (report errors when not defined);
  • discourage the use of reflection by reporting warnings when it is used;
  • automatically transform the program to insert a serialVersionUID field when the class implements Serializable;
  • report an error when a visited class of a visitor pattern does not define the accept method- in general, validate that a design pattern is correctly implemented;
  • transform the body of a setter method annotated with @Persistent to save the object’s state change in a data storage;
  • etc...

With the SpoonJDT Eclipse plugin, all the Spoonlet warnings, errors, and other messages are well-integrated to the Eclipse environment and are reported exactly like regular Java compilation problems. Hence, a Spoonlet can really be seen as a compilation component that enhances the Java language to fit better your development context.

back

Step 1: installing the SpoonJDT plugin

In order to deploy a Spoonlet in your Eclipse environment, you first need to install the SpoonJDT plugin, which is a simple Spoonlet container. This plugin is generic to all the Spoonlets and needs to be installed only once.

Eclipse 3.2 and Java 5 is required to install SpoonJDT plugin. Use the update manager with this URL (http://spoon.gforge.inria.fr/eclipse) to find and install the plugin. More information about using the update manager is available here

back

Step 2: deploying a Spoonlet in Eclipse

SpoonJDT by itself is an empty container and you need to deploy Spoonlets to enable actual program processing. Let us deploy the VSuite findbugs Spoonlet on a target project. First, make sure you have a Java target project (name it target-project) with a few classes that contain some coding problems. Note: we recommend that you start this tutorial in a brand new Eclipse workspace.

We recommend that you create an src source folder. The created class is example.MyClass:

package example;

public class MyClass {
    private int i = 1;
    public void test() {
        if (5 > 2) {
            i = 1 + i++ - 5;
        } 
    }
    @Override
    public String toString() {
        return super.toString().substring(0);
    }
}

Note: the class shown here contains statements and expressions which are confusing and can be considered as bugs (such as i=i++ or substring(0), which both have no effect); however, Eclipse’s JDT doesn’t report any warnings or errors.

>> screenshot 1 <<

Let us now deploy a Spoonlet that will enhance Java in order to detect this kind of problems.

VSuite is a project that aims at finding and reporting problems in your Java code. The provided findbugs Spoonlet contains some of the bugs covered by the well-known FindBugs project. The following steps describe how to use this Spoonlet in a target Eclipse Java project.

  • Get the latest version of vsuite-findbugs-xx.jar that you will find on the Spoon website and store it in target-project (only local jars are supported at the moment).
  • Right click on the target-project Java project root icon (make sure you are in Java perspective). Go to Properties -> Java compiler -> Spoon.
  • Enable Spoon processing and add vsuite-findbugs-xx.jar as a local jar. Several Spoon processors are available, each one performing a specific validation: you can enable/disable them and (sometimes) apply some configuration.

>> screenshot 2 <<

  • Press the "Apply" or "OK" button. Processors are then automatically applied to the target Java project (messages are reported when needed).

>> screenshot 3 <<

Repeat this process for deploying other Spoonlets.

Finally, note that SpoonJDT allows Spoonlets to be run in 4 modes:

  • Default: the code generation step, if any, is hidden to the user (this mode is recommended);
  • Advanced: the code is generated and accessible in a target directory;
  • NoOutput: no code is generated (not recommended, but useful when a Spoonlet is only validating the program, like VSuite and AVal Spoonlets);
  • NoCompile: Spoon generates but does not compile the code (useful when generating code in another project, which has its own compiling environment).

back

Available Spoonlets

Anyone can create and distribute Spoonlets. The Spoon project provides some useful, general-purpose Spoonlets described here

back

Creating your own Spoonlet

It is often the case that general purpose validations or transformations are not the most interesting for a given development context. For instance, you may want to enhance Java to take into account framework-specific idioms or patterns. These may vary depending on your environment (colleagues, company, customers, frameworks). That’s why it is interesting to create your own Spoonlet to ensure some environmental programming conventions for yourself, your colleagues, your customers, code auditors, freelance developers, outsourced developments, etc.

back

Programming a processor

Before moving forward, let us create a Java project called spoonlet-project to work in. Note that to access the Spoon API, spoon.jar must be in your classpath. When using the SpoonJDT plugin, the right spoon.jar will be automatically added to your project’s classpath when Spoon processing is activated.

To create a Spoonlet, you first need to program a processor. A processor is usually a kind of scanner that traverses the program’s AST (Abstract Syntax Tree) in order to statically validate or transform the program. With the Spoon API, the AST is defined in the spoon.reflect sub-packages and gives access to the entire Java program, both for reading and modification. In the case you are working with Eclipse, your AST will correspond to the code of the currently processed Java project.

A processor corresponds to a Java class that implements spoon.processing.Processor<T> and defines the process method. In Spoon, the processor is parameterized by a T type parameter (a.k.a. generics) that gives the type of program element to be processed. If you want to process all the elements of your program, you set T to CtElement: the elements’ root. If you only need to traverse the method declarations, you set T to CtMethod. As an example, let us program a processor that will report warnings when public methods are not documented with a Javadoc comment.

package example;

import spoon.processing.AbstractProcessor;
import spoon.processing.Severity;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.ModifierKind;

public class JavadocProcessor extends AbstractProcessor<CtMethod<?>> {
    public void process(CtMethod<?> m) {
        if (m.getModifiers().contains(ModifierKind.PUBLIC)) {
            if (m.getDocComment() == null || m.getDocComment().equals("")) {
                getEnvironment().report(this, Severity.WARNING, m,
                    "undocumented method");
            }
        }
    }
}

back

Packaging a Spoonlet

To be deployed in a Spoonlet container such as Eclipse’s SpoonJDT, the Spoonlet jar file must contains a spoon.xml deployment descriptor. This file describes the available processors that should be used to validate or transform the program. Note that processors are applied in the order they are declared in the deployment descriptor. In addition, they can be set to active, mandatory, and visible. When not active, the processor is not applied to the program by default and must be activated manually by the user through the UI. When mandatory, the processor cannot be deactivated by the user. When not visible, the processor is not shown to the final user so that it cannot be (de)activated.

Note that each processor may define a set of UI-configurable properties, of which default values can be set in the deployment descriptor. Properties will be explained in a further section. Also, when a Spoonlet implements automatic program transformations using Spoon templates, it must specify a location for the template source files.

For our simple example that tests for Javadoc on public methods, the spoon.xml file will look like:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE spoon SYSTEM "http://spoon.gforge.inria.fr/pub/xml/spoonlet.dtd">
<spoon>
  <processor name="Javadoc Processor"
    class="example.JavadocProcessor" active="true"
    doc="Checks that a public method is documented with a Javadoc comment">
  </processor>
</spoon>

Then, to package the Spoonlet, you just need to create a jar that contains the compiled processor classes (and any utility classes) and the spoon.xml file, which can be located anywhere in the directory structure. Right click on the project and choose “Export…”.

>> screenshot 5 <<

>> screenshot 6 <<

Find more information on how to create a jar file here

Finally, you can apply this Spoonlet to you target project (such as the one created at the beginning of this tutorial – you can remove the vsuite-findbugs Spoonlet for clarity).

>> screenshot 7 <<

Once applied, undocumented public methods will raise warnings.

>> screenshot 8 <<

back

Developing Processors

In this section, we will describe how to develop efficiently configurable processors for validation.

back

Development mode

When developing processors, it is not convenient to package and deploy Spoonlets each time a change has to be done. SpoonJDT provides a way to import Spoonlets from an Eclipse Java project of the current workspace. To do so, in the Spoon properties of target-project, use “Add descriptors…” and select the spoon.xml file that corresponds to your under-development Spoonlet in spoonlet-project. When the Javadoc processor will be changed, it will be automatically reloaded by the plugin when target-project is cleaned.

>> screenshot 9 <<

As an example, you can try to modify the reported message of JavadocProcessor and clean target-project: the warnings should be automatically updated. In the following screenshot, we simply add exclamation marks to the reported warning message and clean target-project.

>> screenshot 10 <<

back

Configurable Processors

Processors can be used as is, but most of the time, the users will wish to adapt their scope and behavior to better fit their needs, depending on the developed project and development environment. There are two ways a processor can be parameterized by the user:

  • at a processor level with properties
  • at a source code level with annotations (metadata)

Property-driven Configuration

A property is a field defined by a processor class and annotated by @Property. Default values for these properties can be defined directly in Java, but also in the spoon.xml file (which overrides the Java default values). As an example, let us modify the Javadoc processor example to add a property that will specify if the processor should report an error or a warning. To do this, the best is to add a severity property.

package example;

import spoon.processing.AbstractProcessor;
import spoon.processing.Property;
import spoon.processing.Severity;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.ModifierKind;

public class JavadocProcessor extends AbstractProcessor<CtMethod<?>> {
    @Property
    Severity severity = Severity.WARNING;
    public void process(CtMethod<?> m) {
        if (m.getModifiers().contains(ModifierKind.PUBLIC)) {
            if (m.getDocComment() == null || m.getDocComment().equals("")) {
                getEnvironment().report(this, severity, m,
                    "undocumented method");
            }
        }
    }
}

Then, you can set the default value in the spoon.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE spoon SYSTEM "spoonlet.dtd">

<spoon>
  <processor name="Javadoc Processor"
    class="example.JavadocProcessor" active="true"
    doc="Checks that a public method is documented with a Javadoc comment">
    <property name="severity" doc="Severity of reported message" 
              value="WARNING" />
  </processor>
</spoon>

The interesting point of this configuration method is to enable configuration that will impact your project in a global way. Here, you can set all undocumented methods to be reported as warnings or errors.

>> screenshot 11 <<

>> screenshot 12 <<

Annotation-driven Configuration

Another way to configure processors is to make them annotation-sensitive. As an example, you can use @SuppressSpoonWarnings("javadoc") that will make the processor to report nothing when the method is annotated with this annotation.

package example;

public @interface SuppressSpoonWarnings {
	String[] value();
}

package example;

import java.util.Arrays;
import spoon.processing.AbstractProcessor;
import spoon.processing.Severity;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.ModifierKind;

public class JavadocProcessor extends AbstractProcessor<CtMethod<?>> {
    public void process(CtMethod<?> m) {
        SuppressSpoonWarnings sw = m.getAnnotation(SuppressSpoonWarnings.class);
        if (sw != null && Arrays.asList(sw.value()).contains("javadoc")) {
            return;
        }
        if (m.getModifiers().contains(ModifierKind.PUBLIC)) {
            if (m.getDocComment() == null || m.getDocComment().equals("")) {
                getEnvironment().report(this, Severity.WARNING, m,
                    "undocumented method");
            }
        }
    }
}

Contrary to property-driven configuration, this configuration method allows for specific configuration with a local scope (for instance, a given method or class). Of course, property and annotation-driven configuration can be used within the same processor.

>> screenshot 13 <<

back

Quick Fixes

When a problem has been detected by a processor, it is often the case that one or several fixes can be provided to solve it in an automatic way. These are called “quick fixes” under Eclipse and users can choose what proposal will be applied. Spoon, provides an API to define and propose fixes while reporting problems. To do so, you shall first implement the ProblemFixer interface as shown below. Here, we specify a simple quick fix for our Javadoc example that generates a simple default Javadoc comment. The run method and pass one or more instances to the report method.

package example;

import spoon.processing.AbstractProblemFixer;
import spoon.reflect.Changes;
import spoon.reflect.declaration.CtMethod;

public class JavadocFixer extends AbstractProblemFixer<CtMethod<?>> {
    public String getDescription() {
        return "Create a default javadoc for method";
    }
    public String getLabel() {
        return "Add Javadoc";
    }
    public Changes run(CtMethod<?> m) {
        m.setDocComment(m.getSimpleName()
                        + ": automatically generated comment");
        Changes c = new Changes();
        c.getModified().add(m.getParent(CtSimpleType.class));
        return c;
    }
}

You should propose this fix while reporting the problem:

package example;

import spoon.processing.AbstractProcessor;
import spoon.processing.Severity;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.ModifierKind;

public class JavadocProcessor extends AbstractProcessor<CtMethod<?>> {
    public void process(CtMethod<?> m) {
        if (m.getModifiers().contains(ModifierKind.PUBLIC)) {
            if (m.getDocComment() == null || m.getDocComment().equals("")) {
                getEnvironment().report(this, Severity.WARNING, m,
                    "undocumented method", new JavadocFixer());
            }
        }
    }
}

>> screenshot 14 <<

>> screenshot 15 <<

>> screenshot 16 <<

back

Generative Programming with Spoon

Generative programming is a disciple that consists of automatically generating new code out of parameterized specifications, such as generic classes or templates. This can be useful when the development environment (frameworks, libraries) requires the programmer to repeat the same coding tasks over and over again. For this purpose, Spoon provides a template mechanism in pure Java that allows for well-typed and cleanly-defined code transformation and generation.

back

Template Principles

A Spoon pure-Java template is a regular Java class that contains template parameters (or variation points). These template parameters are defined as fields annotated with @Parameter. Template parameters can represent primitive values (such as literal values, program element's names, types), or actual program elements (CtElement). In the template code, all the references to template parameters can be substituted by their actual values using the substitution engine API provided by Spoon.

While primitive template parameters can be accessed directly, non-primitive template parameters must implement the TemplateParameter<T> interface, where T is the actual type of the parameter. Within a template, a non-primitive template parameter is referenced by invoking TemplateParameter.S() on the parameter. The return type of S() is T, which allows for the definition of well-typed template expressions.

back

A Simple Template

Let us assume that on the MyClass.test() method as defined earlier in target-project, we want to add a precondition that enforces the MyClass.i to be greater than 0. We could hardcode this condition in the MyClass.test() implementation but is can be interesting to used generative programming to automatically insert it.

Using Spoon templates, we can write the template code that checks for the precondition as follows.

package example;

import spoon.template.Local;
import spoon.template.Template;

public class PreconditionTemplate implements Template {
    @Local int i;
    void precondition() {
        if(!(i>0)) {
            throw new RuntimeException("precondition not fulfilled");
        }
    }
}

This template has to be inserted at the beginning of test(). This is the role of the following processor:

package example;

import spoon.processing.AbstractProcessor;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtMethod;
import spoon.template.Substitution;

public class PreconditionProcessor extends AbstractProcessor<CtMethod> {
    public void process(CtMethod m) {
        if (m.getReference().toString().equals("void example.MyClass#test()")) {
            m.getBody().insertBegin(
                Substitution.substituteMethodBody((CtClass) m
                    .getDeclaringType(), new PreconditionTemplate(),
                    "precondition"));
        }
    }
}

In order to activate the precondition, you need to declare a new processor in your spoon.xml descriptor. Also, the path of your template source must be indicated here, so that Spoon can create the template AST and use it in the substitution. The path shall be indicated relatively to an Eclipse source folder.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE spoon SYSTEM "spoonlet.dtd">

<spoon>
  <processor name="Javadoc Processor"
    class="example.JavadocProcessor" active="true"
    doc="Checks that a public method is documented with a Javadoc comment">
    <property name="severity" doc="Severity of reported message" 
              value="WARNING" />
  </processor>
  <processor name="Precondition Processor"
    class="example.PreconditionProcessor" active="true"
    doc="Checks a precondition before the execution of the test() method">
  </processor>
  <template path="example/PreconditionTemplate.java" />
</spoon>

You can now check that the template has been applied to your program by adding a run method that invokes MyClass.test(). For example:

package example;

public class MyClass {
    public static void main(String[] args) {
        System.out.println("invoking test()...");
        new MyClass().test();
        System.out.println("done.");
    }
[...]

As the default value of MyClass.i is 0, the precondition is not fulfilled and a runtime exception will occur when running this program (click right on MyClass and choose “Run as.,. => Java application”).

>> screenshot 17 <<

Note that you can see the generated code by switching to the advanced mode in the Spoon properties window. The default location is the spooned directory.

>> screenshot 18 <<

back