Coder Social home page Coder Social logo

javapoet's Introduction

JavaPoet

JavaPoet is a Java API for generating .java source files.

Source file generation can be useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.

Example

Here's a (boring) HelloWorld class:

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

And this is the (exciting) code to generate it with JavaPoet:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
    .returns(void.class)
    .addParameter(String[].class, "args")
    .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(main)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

To declare the main method, we've created a MethodSpec "main" configured with modifiers, return type, parameters and code statements. We add the main method to a HelloWorld class, and then add that to a HelloWorld.java file.

In this case we write the file to System.out, but we could also get it as a string (JavaFile.toString()) or write it to the file system (JavaFile.writeTo()).

The Javadoc catalogs the complete JavaPoet API, which we explore below.

Code & Control Flow

Most of JavaPoet's API uses plain old immutable Java objects. There's also builders, method chaining and varargs to make the API friendly. JavaPoet offers models for classes & interfaces (TypeSpec), fields (FieldSpec), methods & constructors (MethodSpec), parameters (ParameterSpec) and annotations (AnnotationSpec).

But the body of methods and constructors is not modeled. There's no expression class, no statement class or syntax tree nodes. Instead, JavaPoet uses strings for code blocks:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "  total += i;\n"
        + "}\n")
    .build();

Which generates this:

void main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += i;
  }
}

The manual semicolons, line wrapping, and indentation are tedious and so JavaPoet offers APIs to make it easier. There's addStatement() which takes care of semicolons and newline, and beginControlFlow() + endControlFlow() which are used together for braces, newlines, and indentation:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();

This example is lame because the generated code is constant! Suppose instead of just adding 0 to 10, we want to make the operation and range configurable. Here's a method that generates a method:

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 1")
      .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
      .addStatement("result = result " + op + " i")
      .endControlFlow()
      .addStatement("return result")
      .build();
}

And here's what we get when we call computeRange("multiply10to20", 10, 20, "*"):

int multiply10to20() {
  int result = 1;
  for (int i = 10; i < 20; i++) {
    result = result * i;
  }
  return result;
}

Methods generating methods! And since JavaPoet generates source instead of bytecode, you can read through it to make sure it's right.

Some control flow statements, such as if/else, can have unlimited control flow possibilities. You can handle those options using nextControlFlow():

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("long now = $T.currentTimeMillis()", System.class)
    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
    .nextControlFlow("else")
    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
    .endControlFlow()
    .build();

Which generates:

void main() {
  long now = System.currentTimeMillis();
  if (System.currentTimeMillis() < now)  {
    System.out.println("Time travelling, woo hoo!");
  } else if (System.currentTimeMillis() == now) {
    System.out.println("Time stood still!");
  } else {
    System.out.println("Ok, time still moving forward");
  }
}

Catching exceptions using try/catch is also a use case for nextControlFlow():

MethodSpec main = MethodSpec.methodBuilder("main")
    .beginControlFlow("try")
    .addStatement("throw new Exception($S)", "Failed")
    .nextControlFlow("catch ($T e)", Exception.class)
    .addStatement("throw new $T(e)", RuntimeException.class)
    .endControlFlow()
    .build();

Which produces:

void main() {
  try {
    throw new Exception("Failed");
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}

$L for Literals

The string-concatenation in calls to beginControlFlow() and addStatement is distracting. Too many operators. To address this, JavaPoet offers a syntax inspired-by but incompatible-with String.format(). It accepts $L to emit a literal value in the output. This works just like Formatter's %s:

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
      .addStatement("result = result $L i", op)
      .endControlFlow()
      .addStatement("return result")
      .build();
}

Literals are emitted directly to the output code with no escaping. Arguments for literals may be strings, primitives, and a few JavaPoet types described below.

$S for Strings

When emitting code that includes string literals, we can use $S to emit a string, complete with wrapping quotation marks and escaping. Here's a program that emits 3 methods, each of which returns its own name:

public static void main(String[] args) throws Exception {
  TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
      .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
      .addMethod(whatsMyName("slimShady"))
      .addMethod(whatsMyName("eminem"))
      .addMethod(whatsMyName("marshallMathers"))
      .build();

  JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
      .build();

  javaFile.writeTo(System.out);
}

private static MethodSpec whatsMyName(String name) {
  return MethodSpec.methodBuilder(name)
      .returns(String.class)
      .addStatement("return $S", name)
      .build();
}

In this case, using $S gives us quotation marks:

public final class HelloWorld {
  String slimShady() {
    return "slimShady";
  }

  String eminem() {
    return "eminem";
  }

  String marshallMathers() {
    return "marshallMathers";
  }
}

$T for Types

We Java programmers love our types: they make our code easier to understand. And JavaPoet is on board. It has rich built-in support for types, including automatic generation of import statements. Just use $T to reference types:

MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

That generates the following .java file, complete with the necessary import:

package com.example.helloworld;

import java.util.Date;

public final class HelloWorld {
  Date today() {
    return new Date();
  }
}

We passed Date.class to reference a class that just-so-happens to be available when we're generating code. This doesn't need to be the case. Here's a similar example, but this one references a class that doesn't exist (yet):

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");

MethodSpec today = MethodSpec.methodBuilder("tomorrow")
    .returns(hoverboard)
    .addStatement("return new $T()", hoverboard)
    .build();

And that not-yet-existent class is imported as well:

package com.example.helloworld;

import com.mattel.Hoverboard;

public final class HelloWorld {
  Hoverboard tomorrow() {
    return new Hoverboard();
  }
}

The ClassName type is very important, and you'll need it frequently when you're using JavaPoet. It can identify any declared class. Declared types are just the beginning of Java's rich type system: we also have arrays, parameterized types, wildcard types, and type variables. JavaPoet has classes for building each of these:

ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOfHoverboards = ParameterizedTypeName.get(list, hoverboard);

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("result.add(new $T())", hoverboard)
    .addStatement("return result")
    .build();

JavaPoet will decompose each type and import its components where possible.

package com.example.helloworld;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    result.add(new Hoverboard());
    return result;
  }
}

Import static

JavaPoet supports import static. It does it via explicitly collecting type member names. Let's enhance the previous example with some static sugar:

...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add($T.createNimbus(2000))", hoverboard)
    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
    .addStatement("$T.sort(result)", Collections.class)
    .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
    .build();

TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
    .addMethod(beyond)
    .build();

JavaFile.builder("com.example.helloworld", hello)
    .addStaticImport(hoverboard, "createNimbus")
    .addStaticImport(namedBoards, "*")
    .addStaticImport(Collections.class, "*")
    .build();

JavaPoet will first add your import static block to the file as configured, match and mangle all calls accordingly and also import all other types as needed.

package com.example.helloworld;

import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(createNimbus(2000));
    result.add(createNimbus("2001"));
    result.add(createNimbus(THUNDERBOLT));
    sort(result);
    return result.isEmpty() ? emptyList() : result;
  }
}

$N for Names

Generated code is often self-referential. Use $N to refer to another generated declaration by its name. Here's a method that calls another:

public String byteToHex(int b) {
  char[] result = new char[2];
  result[0] = hexDigit((b >>> 4) & 0xf);
  result[1] = hexDigit(b & 0xf);
  return new String(result);
}

public char hexDigit(int i) {
  return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}

When generating the code above, we pass the hexDigit() method as an argument to the byteToHex() method using $N:

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();

Code block format strings

Code blocks may specify the values for their placeholders in a few ways. Only one style may be used for each operation on a code block.

Relative Arguments

Pass an argument value for each placeholder in the format string to CodeBlock.add(). In each example, we generate code to say "I ate 3 tacos"

CodeBlock.builder().add("I ate $L $L", 3, "tacos")

Positional Arguments

Place an integer index (1-based) before the placeholder in the format string to specify which argument to use.

CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)

Named Arguments

Use the syntax $argumentName:X where X is the format character and call CodeBlock.addNamed() with a map containing all argument keys in the format string. Argument names use characters in a-z, A-Z, 0-9, and _, and must start with a lowercase character.

Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)

Methods

All of the above methods have a code body. Use Modifiers.ABSTRACT to get a method without any body. This is only legal if the enclosing class is either abstract or an interface.

MethodSpec flux = MethodSpec.methodBuilder("flux")
    .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addMethod(flux)
    .build();

Which generates this:

public abstract class HelloWorld {
  protected abstract void flux();
}

The other modifiers work where permitted. Note that when specifying modifiers, JavaPoet uses javax.lang.model.element.Modifier, a class that is not available on Android. This limitation applies to code-generating-code only; the output code runs everywhere: JVMs, Android, and GWT.

Methods also have parameters, exceptions, varargs, Javadoc, annotations, type variables, and a return type. All of these are configured with MethodSpec.Builder.

Constructors

MethodSpec is a slight misnomer; it can also be used for constructors:

MethodSpec flux = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "greeting")
    .addStatement("this.$N = $N", "greeting", "greeting")
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(flux)
    .build();

Which generates this:

public class HelloWorld {
  private final String greeting;

  public HelloWorld(String greeting) {
    this.greeting = greeting;
  }
}

For the most part, constructors work just like methods. When emitting code, JavaPoet will place constructors before methods in the output file.

Parameters

Declare parameters on methods and constructors with either ParameterSpec.builder() or MethodSpec's convenient addParameter() API:

ParameterSpec android = ParameterSpec.builder(String.class, "android")
    .addModifiers(Modifier.FINAL)
    .build();

MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
    .addParameter(android)
    .addParameter(String.class, "robot", Modifier.FINAL)
    .build();

Though the code above to generate android and robot parameters is different, the output is the same:

void welcomeOverlords(final String android, final String robot) {
}

The extended Builder form is necessary when the parameter has annotations (such as @Nullable).

Fields

Like parameters, fields can be created either with builders or by using convenient helper methods:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(android)
    .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
    .build();

Which generates:

public class HelloWorld {
  private final String android;

  private final String robot;
}

The extended Builder form is necessary when a field has Javadoc, annotations, or a field initializer. Field initializers use the same String.format()-like syntax as the code blocks above:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .initializer("$S + $L", "Lollipop v.", 5.0d)
    .build();

Which generates:

private final String android = "Lollipop v." + 5.0;

Interfaces

JavaPoet has no trouble with interfaces. Note that interface methods must always be PUBLIC ABSTRACT and interface fields must always be PUBLIC STATIC FINAL. These modifiers are necessary when defining the interface:

TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
        .initializer("$S", "change")
        .build())
    .addMethod(MethodSpec.methodBuilder("beep")
        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
        .build())
    .build();

But these modifiers are omitted when the code is generated. These are the defaults so we don't need to include them for javac's benefit!

public interface HelloWorld {
  String ONLY_THING_THAT_IS_CONSTANT = "change";

  void beep();
}

Enums

Use enumBuilder to create the enum type, and addEnumConstant() for each value:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK")
    .addEnumConstant("SCISSORS")
    .addEnumConstant("PAPER")
    .build();

To generate this:

public enum Roshambo {
  ROCK,

  SCISSORS,

  PAPER
}

Fancy enums are supported, where the enum values override methods or call a superclass constructor. Here's a comprehensive example:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
        .addMethod(MethodSpec.methodBuilder("toString")
            .addAnnotation(Override.class)
            .addModifiers(Modifier.PUBLIC)
            .addStatement("return $S", "avalanche!")
            .returns(String.class)
            .build())
        .build())
    .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
        .build())
    .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
        .build())
    .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(MethodSpec.constructorBuilder()
        .addParameter(String.class, "handsign")
        .addStatement("this.$N = $N", "handsign", "handsign")
        .build())
    .build();

Which generates this:

public enum Roshambo {
  ROCK("fist") {
    @Override
    public String toString() {
      return "avalanche!";
    }
  },

  SCISSORS("peace"),

  PAPER("flat");

  private final String handsign;

  Roshambo(String handsign) {
    this.handsign = handsign;
  }
}

Anonymous Inner Classes

In the enum code, we used TypeSpec.anonymousInnerClass(). Anonymous inner classes can also be used in code blocks. They are values that can be referenced with $L:

TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
    .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
    .addMethod(MethodSpec.methodBuilder("compare")
        .addAnnotation(Override.class)
        .addModifiers(Modifier.PUBLIC)
        .addParameter(String.class, "a")
        .addParameter(String.class, "b")
        .returns(int.class)
        .addStatement("return $N.length() - $N.length()", "a", "b")
        .build())
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addMethod(MethodSpec.methodBuilder("sortByLength")
        .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
        .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
        .build())
    .build();

This generates a method that contains a class that contains a method:

void sortByLength(List<String> strings) {
  Collections.sort(strings, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
      return a.length() - b.length();
    }
  });
}

One particularly tricky part of defining anonymous inner classes is the arguments to the superclass constructor. In the above code we're passing the empty string for no arguments: TypeSpec.anonymousClassBuilder(""). To pass different parameters use JavaPoet's code block syntax with commas to separate arguments.

Annotations

Simple annotations are easy:

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hoverboard")
    .build();

Which generates this method with an @Override annotation:

  @Override
  public String toString() {
    return "Hoverboard";
  }

Use AnnotationSpec.builder() to set properties on annotations:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(Headers.class)
        .addMember("accept", "$S", "application/json; charset=utf-8")
        .addMember("userAgent", "$S", "Square Cash")
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();

Which generates this annotation with accept and userAgent properties:

@Headers(
    accept = "application/json; charset=utf-8",
    userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);

When you get fancy, annotation values can be annotations themselves. Use $L for embedded annotations:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(HeaderList.class)
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "Accept")
            .addMember("value", "$S", "application/json; charset=utf-8")
            .build())
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "User-Agent")
            .addMember("value", "$S", "Square Cash")
            .build())
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();

Which generates this:

@HeaderList({
    @Header(name = "Accept", value = "application/json; charset=utf-8"),
    @Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);

Note that you can call addMember() multiple times with the same property name to populate a list of values for that property.

Javadoc

Fields, methods and types can be documented with Javadoc:

MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
    .addJavadoc("Hides {@code message} from the caller's history. Other\n"
        + "participants in the conversation will continue to see the\n"
        + "message in their own history unless they also delete it.\n")
    .addJavadoc("\n")
    .addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
        + "conversation for all participants.\n", Conversation.class)
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addParameter(Message.class, "message")
    .build();

Which generates this:

  /**
   * Hides {@code message} from the caller's history. Other
   * participants in the conversation will continue to see the
   * message in their own history unless they also delete it.
   *
   * <p>Use {@link #delete(Conversation)} to delete the entire
   * conversation for all participants.
   */
  void dismiss(Message message);

Use $T when referencing types in Javadoc to get automatic imports.

Download

Download the latest .jar or depend via Maven:

<dependency>
  <groupId>com.squareup</groupId>
  <artifactId>javapoet</artifactId>
  <version>1.13.0</version>
</dependency>

or Gradle:

compile 'com.squareup:javapoet:1.13.0'

Snapshots of the development version are available in Sonatype's snapshots repository.

License

Copyright 2015 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

JavaWriter

JavaPoet is the successor to JavaWriter. New projects should prefer JavaPoet because it has a stronger code model: it understands types and can manage imports automatically. JavaPoet is also better suited to composition: rather than streaming the contents of a .java file top-to-bottom in a single pass, a file can be assembled as a tree of declarations.

JavaWriter continues to be available in GitHub and Maven Central.

javapoet's People

Contributors

aleckazakova avatar auke- avatar ben-manes avatar benjamin-bader avatar caplan avatar cconroy avatar cgruber avatar christianreynoldsdr avatar danglotb avatar dependabot[bot] avatar eamonnmcmanus avatar egorand avatar galderz avatar gfx avatar gk5885 avatar jakewharton avatar jgustie avatar nightlynexus avatar octylfractal avatar ragunathjawahar avatar renovate[bot] avatar ronshapiro avatar shaishavgandhi avatar sormuras avatar sullis avatar swankjesse avatar tbroyer avatar thesurlydev avatar whiskeysierra avatar zacsweers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

javapoet's Issues

Thoughtful ordering of members

Right now static methods are emitted below constructors.

Seems like the only logical grouping we should make. There are other one's in practice (public static methods before lesser scoped static methods, public instance methods before lesser scoped instance methods, etc.) but I don't think they should be addressed at this time (if ever).

TypeNames.forTypeMirror intersection problem.

javax.lang.model.type.UnknownTypeException: Unknown type: java.lang.Number&java.lang.Runnable
    at javax.lang.model.util.AbstractTypeVisitor6.visitUnknown(AbstractTypeVisitor6.java:150)
    at javax.lang.model.util.AbstractTypeVisitor6.visitIntersection(AbstractTypeVisitor6.java:133)
    at com.sun.tools.javac.code.Type$IntersectionClassType.accept(Type.java:1019)
    at com.squareup.javawriter.TypeNames.forTypeMirror(TypeNames.java:52)
$ mvn -v
Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-11T13:58:10-07:00)
Maven home: /usr/local/Cellar/maven/3.2.3/libexec
Java version: 1.8.0_25, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.9.5", arch: "x86_64", family: "mac"

cc @tbroyer

Multi-member annotations

Currently only logic for a single value exists.

if (!memberMap.isEmpty()) {
  appendable.append('(');
  if (memberMap.size() == 1) {
    Entry<String, Writable> onlyEntry = Iterables.getOnlyElement(memberMap.entrySet());
    if (!onlyEntry.getKey().equals("value")) {
      appendable.append(onlyEntry.getKey()).append(" = ");
    }
    onlyEntry.getValue().write(appendable, context);
  }
  appendable.append(')');
}

Empty method bodies appear abstract.

ClassWriter cw = ClassWriter.forClassName(ClassName.bestGuessFromString("example.Test"));
cw.addMethod(VoidName.VOID, "test");
System.out.println(cw);
package example;

class Test {
  void test();
}

This is unfortunate, unexpected behavior. Only if the method is declared abstract should this behavior happen.

Stable serialVersionUID generation

A cool feature would be the stable generation of a version UID for serialization. We see other code gen implementations change this value all the time because they use things like hash functions on the string output (e.g., jOOQ) which unnecessarily churns this value.

Documentation Support

Javadoc on type, field, method. Would be nice to have top-of-file command for license and/or generated warning as well. In its dumbest form this just need to take a string (or aggregate strings).

Interesting things here might be @param support on a ParameterWriter (VariableWriter subclass)? @throws on a ThrowsWriter? @return somewhere?

Version 3.0 Working Spec

This issue will serve as a working space for what a potential version 3.0 would look like with a new, more dynamic, and more powerful API.

High-Level Goals

  • The ability to defer type compression (imports) until emission.
  • Distinct code blocks as objects for composition.
  • And more, probably...

Deferred Type Compression

Currently, type compression via imports requires knowledge of all types up before the body of the file. In practice, this usually requires an awkward two-pass approach where you determine the imports based on a pass of dynamic contents and then emit the body based on a second pass.

The goal of this is to always emit fully-qualified types from the API and rely on a post-compression phase for emitting imports.

There are multiple ways to approach this. A non-exhaustive list:

  • Type location-aware parsing of code statements combined with type parameters.
  • Method which wraps a type in a token that can later be replaced.
  • Code blocks which are aware of which types they contain. (see below)

Code Block Objects

Objects which represents pieces of code lend themselves naturally to writing emission code in Java. The wins should hopefully be fairly obvious.

There is a potential for API explosion here to cover the diversity of the Java language. A potential also exists for adding an unacceptable amount of boilerplate to the API.

Evaluate source tree writer

In both v3 and v2 knowledge of how contents were being emitted leaks around too far for my liking. In almost all cases of writing you have a destination folder and want normal Java folder and file naming semantics inferred from package and class name.

The one potential exception to this rule is annotation processors which go through the Filer. I don't think we necessarily need to cover that use case, but if it can be done easily that would be a nice touch.

Support Java 8 things

Some failing tests:

  @Test public void interfaceStaticMethod() throws IOException {
    javaWriter.emitPackage("com.squareup");
    javaWriter.beginType("com.squareup.Foo", "interface", EnumSet.noneOf(Modifier.class));
    javaWriter.beginMethod("Foo", "empty", EnumSet.of(STATIC));
    javaWriter.emitStatement("return new Foo() {}");
    javaWriter.endMethod();
    javaWriter.endType();
    assertCode(""
        + "package com.squareup;\n"
        + "\n"
        + "interface Foo {\n"
        + "  static Foo empty() {\n"
        + "    return new Foo() {};\n"
        + "  }\n"
        + "}\n");
  }

  @Test public void interfaceDefaultMethod() throws IOException {
    javaWriter.emitPackage("com.squareup");
    javaWriter.beginType("com.squareup.Foo", "interface", EnumSet.noneOf(Modifier.class));
    javaWriter.beginMethod("String", "asString", EnumSet.of(DEFAULT));
    javaWriter.emitStatement("return toString()");
    javaWriter.endMethod();
    javaWriter.endType();
    assertCode(""
        + "package com.squareup;\n"
        + "\n"
        + "interface Foo {\n"
        + "  default String asString() {\n"
        + "    return toString();\n"
        + "  }\n"
        + "}\n");
  }

Better Error Messages for emitMethod()

Writing Something like this:

  javaWriter.beginMethod("void", "save", Sets.newHashSet(Modifier.PUBLIC), "ContentValues contentValues");

Will thrown an ArrayIndexOutOfBoundsException instead of checking to ensure the parameters are perfectly even. May I suggest a throwable with a better message?

Can't use annotation @Param

Hi,

i use eclipse w/o gradle or maven. I addede groundy as library and compiler as annotation processor to the project. Everything works right except @param.

when i try to use @onProgress or @para i get error.
as i understand compiler use annotation Param but it doesn't present in his classpath.
i tried to add it to the jar but eclipse gets crash.

i can't understand why because in my own processor(AnnotatedSQL project) i use annotaions in compiler without problems

Error:

Errors occurred during the build.
Errors running builder 'Java Builder' on project .
com/telly/groundy/annotations/Param

Thanks,
Gennadiy

Add support for annotated method parameters.

Which of the following API design is better on interface and abstract methods that will support annotated parameters?

javaWriter.beginMethodSignature(returnType, methodName, modifiers);
javaWriter.emitAnnotation(annotationType);
javaWriter.emitParam(paramType, paramName);
javaWriter.endMethodSignature();

or

javaWriter.beginMethodSignature(returnType, methodName, modifiers);
javaWriter.emitAnnotation(annotationType);
javaWriter.emitParam(paramType, paramName);
javaWriter.endMethodSignature();
javaWriter.endMethod(); // <== Regardless of the type definition or modifier??

Support javax.lang.model.element.Modifier

java.lang.reflect.Modifier is pretty gross and makes for a bunch of code littered with ints and ORs. In the land of annotation processing we have a better alternative: http://docs.oracle.com/javase/6/docs/api/javax/lang/model/element/Modifier.html

I'd like to add versions of all of the JavaWriter methods that take Set instead of int. Then, ideally we'd phase out the int methods at some point in the future.

Since it's a fair bit of work, I wanted some consensus before bothering to do all of the work. Any objections?

Add support for nested annotations

Currently emitAnnotation will do a toString() on the value of an annotation attribute. However this becomes a nuisance if the value itself is an annotation instance with its own values.

Is there perhaps a technique (and if not please add it!) to easily define a new annotation as the value of an emitted annotation, that would function in the same way as an emitted annotation?

JavaWriter 3: Blocks within methods aren't indented

If you write something like this:

  methodBody
    .addSnippet("if (%s == null) {", identifier)
    .addSnippet("throw new NullPointerException(\"Null %s\");", identifier)
    .addSnippet("}");

then you might expect that JavaWriter would automatically indent the throw statement but you would be wrong. Since we don't require people to manage indentation manually anywhere else, we shouldn't require it here either. Perhaps BlockWriter should have a method allowing you to write something like this:

  methodBody
    .addBlock("if (%s == null)", identifier)
      .addSnippet("throw new NullPointerException(\"Null %s\");", identifier);

where .addBlock would return a nested BlockWriter. Some tweaks on this idea would be needed to get the usual formatting for if/else, try/catch/finally, etc.

JavaWriter 3: Can't generate or reference classes in the default package

I'm not sure how important it is to be able to generate classes in the default package, but it currently doesn't work because of a couple of issues in JavaWriter.java: it should not generate a package statement in this case, and it should consider className.packageName().isEmpty() as an additional reason not to import className.

Merge process needs tweaking

Can we do a slightly more paused merge process - merging early means that nits and style fixes needed a subsequent pull request and code we wouldn't want in gets in, even if just for a little while.

Maybe we can adopt a "ready to merge" (RTM) convention - like an author's LGTM since not all authors can self-merge.

Interface methods should not emit public, abstract

Not quite sure the right approach to take with this. We can either propagate these implementation details downward into Modifiable or switch to private fields with public getters (e.g., Set<Modifier> modifiers()) which can be overridden to return subsets.

Standardize generated field names based on convention

Currently, the field names are directly derived from the .proto files. However, it would be better if while generating the code JavaWriter could follow some conventions like:

  • Hungarian notation for fields
  • Convert underscore in proto field names to camel case

Right now, a field declared as request_id remains as request_id in Java Code and sometimes feels weird to be calling a function like request_id(21)

VoidTypeName should not exist

As suggested in #109, VoidTypeName suggests that void is a type, which it is not, and allows users to accidentally ask for void fields and parameters. The only place where void can appear is to specify the result of a method in MethodWriter, when the method does not return a value. As an alternative design, we could delete VoidTypeName and instead have the returnType parameter of the MethodWriter constructor be an Optional<TypeName>; or have a second constructor that omits the returnType for use when the method is void; or introduce a new supertype of TypeName called MethodReturn, with a new class VoidReturn that is also a subclass of TypeNameOrVoid.

Should we add LambdaWriter support?

Lambdas are interesting syntactical elements. Should we have first-party support for them or just rely on normal Snippet behavior for them?

A first-party type for supporting lambdas would deal with:

  • Choosing whether or not argument parenthesis should be emitted.
  • Comma-separating the argument list.
  • Type declarations in arguments (e.g., (Runnable r) -> r.run()).
  • Emitting the arrow.
  • Choosing whether or not braces are emitted.

Add support for anonymous inner classes

Currently it doesn't look like there is an easy way to write

MyInterface foo = new MyInterface() {

   @Override
   public void bar() {
      // do something awesome
   }
};

A possible api would be

beginAnonymousInnerClass(String kind);

beginAnonymousInnerClass(String fieldType, String fieldName, Set<Modifier> modifiers, String kind);

endAnonymousInnerClass();

The extended begin method would result in

modifiers fieldType fieldName = new kind() {

Language level?

I'd prefer to write this using Java 7 since 6 is EOL'd for a while now. I know this will be used in a variety of environments, though, so what do others think?

For example, not being able to use java.nio.file.Path and try-with-resources just is no fun!

@gk5885 @tbroyer @cgruber

Enhancement: deferable writing

If JavaWriter provided a StringBuilder that could be written to instead of writing to the Writer, then it could reduce the amount of loops I need to generate code. For example, if I have a list of "model"s. I need to generate a field for each model, and also an if-else-if-else block for each model. Ideally I could iterate the model list once, but if you have to write the lines sequentially through the JavaWriter, then the loop has to be repeated in the different locations in the "file".

I have a workaround that helps in some cases, which is I create a StringBuilder and build into it, but this doesn't resolve all situations, because the JavaWriter doesn't allow arbitrary String insertion.

To be verbose, I have an idea for adding a class that is essentially a StringBuilder that can't be constructed outside of JavaWriter, but can be passed in place of a Writer, allowing chunks of code to be generated in it. Then, the StringBuilder-like class can be written by the JavaWriter where the block should be in the final generated file. The library doesn't manage the StringBuilder-like class, only an agreement that the String in it is built to be valid Java. I'm curious if the library is interested in such a feature.

Add throws clause to beginMethod

I'd like to be able to add throws SomeException clauses to method declarations. I've been poking around and didn't see this functionality. Do you guys not do this at Square (you must be emiting control flows for exceptions)?

Immutables, builders & ClassSpec.builder("Taco")

ClassSpec c = ClassSpec.builder("Taco")   // proto-like pattern
   .addMethod(MethodSpec.builder("toString")
       .addAnnotation(Override.class)
       .modifiers(PUBLIC, FINAL)
       .returns(String.class)
       .code("return \"taco\"")
       .build())
   .addMethod(MethodSpec.builderOverriding(executableElement)
       .addSnippet(Snippet.format("return 0;"))
       .build())
   .build()

End of Line Comment Limited Use

The end-of-line comment has little value with it's current behavior.

w.emitStatement("foo()");
w.emitEndOfLineComment("Call foo!");

produces something like:

    ...
    foo();
// Call foo!
    ...

Its Javadoc is also wrong.

JavaWriter 3: No way to specify superclass

TypeWriter has a field Optional supertype but no way to set it. Also, it probably belongs to ClassWriter rather than TypeWriter, since other kinds of types don't have settable supertypes.

Consolidate MethodWriter and ConstructorWriter logic

They are exactly the same except for return type. Have the same implementation twice is bad news bears. Either refactor into a common-base class, make one extend the other, or just use MethodWriter for constructors.

Imports sometimes incorrect when inheriting nested type of same name

If you are generating a subclass of a class like this...

// Parent.java
class Parent {
  static class Optional {}
}

// Generated Child.java
class Child extends Parent {
  java.util.Optional<String> optionalString() {...}
}

...then it is not correct to import java.util.Optional and abbreviate to "Optional optionalString()", because the inherited nested Optional takes precedence over the import statement. Since JavaWriter typically doesn't know anything about Parent in this case, there should be some way of telling it to fully-qualify a type even if it would otherwise be inclined to import it.

This is an actual case that occurs with AutoValue in code at Google. (AutoValue doesn't use JavaWriter and can't as long as this bug remains.)

address simple name collisions in compressType

When there exists the same named simple type in the package of the class being written and also imports, parameterized types should retain the package.

Failing test case below:

@Test public void compressSimpleNameCollisionInSamePackage() throws IOException {
    javaWriter.emitPackage("denominator");
    javaWriter.emitImports("javax.inject.Provider", "dagger.internal.Binding");
    String actual = javaWriter.compressType("dagger.internal.Binding<denominator.Provider>");
    assertThat(actual).isEqualTo("Binding<denominator.Provider>");
  }

Sensible wrapping, where appropriate

There are parts of code to which we can apply sensible wrapping. Right column break to be configured on JavaWriter.

Candidates include:

  • Class declaration (extends, interfaces)
  • Field initializer (if length exceeds wrap&indent initializer)
  • Method declaration (params, exceptions)

Static imports

public JavaWriter addStaticImport(Class<?> cls, String methodName) { }
private final SetMultimap<ClassName, String> staticImports = LinkedHashMultimap.create();

Should be a bit more straightforward to emit since the logic for actually choosing which imports only applies to the current and parent contexts in the same file. A much smaller scope than regular import resolution.

Consider BlockWriter emitting braces.

I think BlockWriter might be more useful if it was in control if emitting braces. This is a trivial change for methods and ctors, but I think the value is in user snippets as well.

For example,

if (thing) %s

Could take a BlockWriter and emit

if (thing) {
  doSomething();
  doSomethingElse();
  return true;
}

This would work well for switch as well:

case "hello": %s
case "hi": %s

Could emit:

case "hello": {
  String name = getName();
  return "Hello, " + name;
}
case "hi": {
  String name = getName();
  return "Hi, " + name;
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.