Friday 2 December 2011

Java Multiline String

Here is an implementation of multiline string literals in Java, using Javadoc comments, an annotation and an annotation processor.

This method works by annotating a String field with a @Multiline annotation, placing the fields initialisation value in a Javadoc comment, then using an annotation processor to set the fields value to the contents of the Javadoc comment at compile time.

First off, the @Multiline annotation.

Multiline.java

package org.adrianwalker.multilinestring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Multiline {
}

Next, the annotation processor to insert the value of the Javadoc comment into the String field.

MultilineProcessor.java

package org.adrianwalker.multilinestring;

import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;

@SupportedAnnotationTypes({"org.adrianwalker.multilinestring.Multiline"})
public final class MultilineProcessor extends AbstractProcessor {

  private JavacElements elementUtils;
  private TreeMaker maker;

  @Override
  public void init(final ProcessingEnvironment procEnv) {
    
    super.init(procEnv);

    JavacProcessingEnvironment javacProcessingEnv = (JavacProcessingEnvironment) procEnv;
    this.elementUtils = javacProcessingEnv.getElementUtils();
    this.maker = TreeMaker.instance(javacProcessingEnv.getContext());
  }

  @Override
  public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {

    Set<? extends Element> fields = roundEnv.getElementsAnnotatedWith(Multiline.class);
    for (Element field : fields) {
      String docComment = elementUtils.getDocComment(field);
      if (null != docComment) {
        JCTree.JCVariableDecl fieldNode = (JCTree.JCVariableDecl) elementUtils.getTree(field);
        fieldNode.init = maker.Literal(docComment);
      }
    }

    return true;
  }
}

And finally, a class which demonstrates the usage of the annotation.

MultilineStringUsage.java

package org.adrianwalker.multilinestring;

public final class MultilineStringUsage {

  /**
  <html>
    <head/>
    <body>
      <p>
        Hello<br/>
        Multiline<br/>
        World<br/>
      </p>
    </body>
  </html>
  */
  @Multiline
  private static String html;
   
  public static void main(final String[] args) {
    System.out.println(html);
  }
}

Maven

Remember to include an <annotationProcessor> element when compiling classes which use @Multiline in the compiler plugin configuration when using Maven.

pom.xml

...
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>2.3.2</version>
  <configuration>
    <source>1.6</source>
    <target>1.6</target>
    <annotationProcessors>
      <annotationProcessor>
        org.adrianwalker.multilinestring.MultilineProcessor
      </annotationProcessor>
    </annotationProcessors>
  </configuration>
</plugin>
...

Eclipse

To use in a non Maven Java project with Eclipse, try this: https://github.com/benelog/multiline/wiki/Non-Maven-Java-project-with-Eclipse

Source Code

Usage

Build and install the Multiline String project which contains the @Multiline annotation and annotation processor, using 'mvn clean install'.

Build and install the Multiline String Usage project which contains an example of how to use the @Multiline annotation, using 'mvn clean install'.

Run the MultilineStringUsage class to output the multiline string.