clean up and reformat code, integrate javapoet, lift tests to java modules

This commit is contained in:
Jörg Prante 2024-03-04 22:29:32 +01:00
parent f5d00d8913
commit b2fda364b5
327 changed files with 9031 additions and 2068 deletions

View file

@ -16,14 +16,31 @@ jar {
duplicatesStrategy = DuplicatesStrategy.INCLUDE duplicatesStrategy = DuplicatesStrategy.INCLUDE
} }
tasks.withType(JavaCompile) { tasks.withType(JavaCompile) {
doFirst {
options.fork = true options.fork = true
options.forkOptions.jvmArgs += ['-Duser.language=en','-Duser.country=US'] options.forkOptions.jvmArgs += ['-Duser.language=en','-Duser.country=US']
options.compilerArgs.add('-Xlint:all')
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
options.compilerArgs.add('-Xlint:all')
// enforce presence of module-info.java
options.compilerArgs.add("--module-path")
options.compilerArgs.add(classpath.asPath)
classpath = files()
}
} }
tasks.withType(Javadoc) { tasks.withType(Javadoc) {
doFirst {
options.addStringOption('Xdoclint:none', '-quiet') options.addStringOption('Xdoclint:none', '-quiet')
options.encoding = 'UTF-8' options.encoding = 'UTF-8'
}
}
tasks.withType(JavaExec) {
doFirst {
jvmArguments.add("--module-path")
jvmArguments.add(classpath.asPath)
classpath = files()
}
} }

View file

@ -8,17 +8,11 @@ dependencies {
test { test {
useJUnitPlatform() useJUnitPlatform()
failFast = false failFast = false
jvmArgs '--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED', // for mockito
'--add-exports=java.base/jdk.internal.ref=ALL-UNNAMED', jvmArgs '--add-modules=jdk.unsupported',
'--add-exports=java.base/sun.nio.ch=ALL-UNNAMED', '--add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED',
'--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED', '--add-opens=jdk.unsupported/sun.reflect=ALL-UNNAMED'
'--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED',
'--add-opens=jdk.compiler/com.sun.tools.javac=ALL-UNNAMED',
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.lang.reflect=ALL-UNNAMED',
'--add-opens=java.base/java.io=ALL-UNNAMED',
'--add-opens=java.base/java.nio=ALL-UNNAMED',
'--add-opens=java.base/java.util=ALL-UNNAMED'
systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties' systemProperty 'java.util.logging.config.file', 'src/test/resources/logging.properties'
testLogging { testLogging {
events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED' events 'STARTED', 'PASSED', 'FAILED', 'SKIPPED'

View file

@ -1,5 +1,16 @@
dependencies { dependencies {
implementation libs.jsoup implementation libs.jsoup
implementation libs.javapoet implementation project(':javapoet')
testImplementation testLibs.mockito.core testImplementation testLibs.mockito.core
} }
def moduleName = 'org.xbib.j2html.codegen.test'
def patchArgs = ['--patch-module', "$moduleName=" + files(sourceSets.test.resources.srcDirs).asPath ]
tasks.named('compileTestJava') {
options.compilerArgs += patchArgs
}
tasks.named('test') {
jvmArgs += patchArgs
}

View file

@ -0,0 +1,7 @@
module org.xbib.j2html.codegen {
requires org.xbib.javapoet;
requires java.compiler;
exports org.xbib.j2html.codegen;
exports org.xbib.j2html.codegen.generators;
exports org.xbib.j2html.codegen.model;
}

View file

@ -1,12 +1,12 @@
package org.xbib.j2html.codegen; package org.xbib.j2html.codegen;
import com.squareup.javapoet.ClassName; import org.xbib.javapoet.ClassName;
import com.squareup.javapoet.JavaFile; import org.xbib.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec; import org.xbib.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName; import org.xbib.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName; import org.xbib.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec; import org.xbib.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName; import org.xbib.javapoet.TypeVariableName;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.HashMap; import java.util.HashMap;

View file

@ -0,0 +1,12 @@
module org.xbib.j2html.codegen.test {
requires transitive org.junit.jupiter.api;
requires org.jsoup;
requires org.mockito;
requires org.xbib.javapoet;
requires java.compiler;
requires org.xbib.j2html.codegen;
exports org.xbib.j2html.codegen.test;
exports org.xbib.j2html.codegen.test.wattsi;
opens org.xbib.j2html.codegen.test;
opens org.xbib.j2html.codegen.test.wattsi;
}

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.codegen; package org.xbib.j2html.codegen.test;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -6,12 +6,15 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.codegen.Generator;
import org.xbib.j2html.codegen.Model;
import org.xbib.j2html.codegen.Parser;
public class GenerateTest { public class GenerateTest {
@Test @Test
public void generate() throws IOException { public void generate() throws IOException {
try (InputStream inputStream = getClass().getResourceAsStream("/html.model")) { try (InputStream inputStream = getClass().getResourceAsStream("html.model")) {
String definitions = new String(inputStream.readAllBytes()); String definitions = new String(inputStream.readAllBytes());
Model model = new Model(); Model model = new Model();
Parser.parse(definitions, model); Parser.parse(definitions, model);

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.codegen; package org.xbib.j2html.codegen.test;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.InOrder; import org.mockito.InOrder;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.codegen.wattsi; package org.xbib.j2html.codegen.test.wattsi;
public interface AttributeDefinition { public interface AttributeDefinition {

View file

@ -1,17 +1,14 @@
package org.xbib.j2html.codegen; package org.xbib.j2html.codegen.test.wattsi;
import org.xbib.j2html.codegen.wattsi.WattsiSource; import java.io.InputStream;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.codegen.generators.TagCreatorCodeGenerator; import org.xbib.j2html.codegen.generators.TagCreatorCodeGenerator;
import org.xbib.j2html.codegen.wattsi.ElementDefinition;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -26,8 +23,8 @@ public class CodeGeneratorComplianceTests {
@BeforeAll @BeforeAll
public static void setUp() throws IOException { public static void setUp() throws IOException {
Path source = Paths.get("src","test","resources","2022-01.wattsi"); InputStream inputStream = CodeGeneratorComplianceTests.class.getResourceAsStream("2022-01.wattsi");
Document doc = Jsoup.parse(source.toFile(), "UTF-8", "https://html.spec.whatwg.org/"); Document doc = Jsoup.parse(inputStream, "UTF-8", "https://html.spec.whatwg.org/");
specification = new WattsiSource(doc); specification = new WattsiSource(doc);
} }

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.codegen.wattsi; package org.xbib.j2html.codegen.test.wattsi;
public interface ElementDefinition { public interface ElementDefinition {

View file

@ -1,8 +1,8 @@
package org.xbib.j2html.codegen.wattsi; package org.xbib.j2html.codegen.test.wattsi;
import com.squareup.javapoet.ClassName; import org.xbib.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec; import org.xbib.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec; import org.xbib.javapoet.TypeSpec;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
@ -21,7 +21,7 @@ public class WattsiGenerator {
List<AttributeDefinition> attributes = wattsi.attributeDefinitions(); List<AttributeDefinition> attributes = wattsi.attributeDefinitions();
for (ElementDefinition element : elements) { for (ElementDefinition element : elements) {
ClassName className = ClassName.get( ClassName className = ClassName.get(
"com.j2html", "org.xbib.j2html",
capitalize(element.name()) + "Tag" capitalize(element.name()) + "Tag"
); );
TypeSpec.Builder type = TypeSpec.classBuilder(className) TypeSpec.Builder type = TypeSpec.classBuilder(className)

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.codegen.wattsi; package org.xbib.j2html.codegen.test.wattsi;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;

View file

@ -3,3 +3,14 @@ dependencies {
testImplementation testLibs.velocity testImplementation testLibs.velocity
testImplementation testLibs.junit.benchmarks testImplementation testLibs.junit.benchmarks
} }
def moduleName = 'org.xbib.j2html.test'
def patchArgs = ['--patch-module', "$moduleName=" + files(sourceSets.test.resources.srcDirs).asPath ]
tasks.named('compileTestJava') {
options.compilerArgs += patchArgs
}
tasks.named('test') {
jvmArgs += patchArgs
}

View file

@ -1,5 +1,6 @@
package org.xbib.j2html; package org.xbib.j2html;
import java.util.Collections;
import org.xbib.j2html.utils.CSSMin; import org.xbib.j2html.utils.CSSMin;
import org.xbib.j2html.utils.EscapeUtil; import org.xbib.j2html.utils.EscapeUtil;
import org.xbib.j2html.utils.Indenter; import org.xbib.j2html.utils.Indenter;
@ -7,8 +8,6 @@ import org.xbib.j2html.utils.JSMin;
import org.xbib.j2html.utils.Minifier; import org.xbib.j2html.utils.Minifier;
import org.xbib.j2html.utils.TextEscaper; import org.xbib.j2html.utils.TextEscaper;
import java.util.Collections;
public class Config { public class Config {
/** /**
@ -97,31 +96,31 @@ public class Config {
return _indenter; return _indenter;
} }
public Config withTextEscaper(TextEscaper textEscaper){ public Config withTextEscaper(TextEscaper textEscaper) {
Config copy = new Config(this); Config copy = new Config(this);
copy._textEscaper = textEscaper; copy._textEscaper = textEscaper;
return copy; return copy;
} }
public Config withCssMinifier(Minifier cssMinifier){ public Config withCssMinifier(Minifier cssMinifier) {
Config copy = new Config(this); Config copy = new Config(this);
copy._cssMinifier = cssMinifier; copy._cssMinifier = cssMinifier;
return copy; return copy;
} }
public Config withJsMinifier(Minifier jsMinifier){ public Config withJsMinifier(Minifier jsMinifier) {
Config copy = new Config(this); Config copy = new Config(this);
copy._jsMinifier = jsMinifier; copy._jsMinifier = jsMinifier;
return copy; return copy;
} }
public Config withEmptyTagsClosed(boolean closeEmptyTags){ public Config withEmptyTagsClosed(boolean closeEmptyTags) {
Config copy = new Config(this); Config copy = new Config(this);
copy._closeEmptyTags = closeEmptyTags; copy._closeEmptyTags = closeEmptyTags;
return copy; return copy;
} }
public Config withIndenter(Indenter indenter){ public Config withIndenter(Indenter indenter) {
Config copy = new Config(this); Config copy = new Config(this);
copy._indenter = indenter; copy._indenter = indenter;
return copy; return copy;

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,12 @@
package org.xbib.j2html.attributes; package org.xbib.j2html.attributes;
import java.io.IOException;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.TagBuilder; import org.xbib.j2html.rendering.TagBuilder;
import org.xbib.j2html.tags.Renderable; import org.xbib.j2html.tags.Renderable;
import java.io.IOException;
public class Attribute implements Renderable { public class Attribute implements Renderable {
private String name; private final String name;
private String value; private String value;
public Attribute(String name, String value) { public Attribute(String name, String value) {

View file

@ -1,10 +1,9 @@
package org.xbib.j2html.rendering; package org.xbib.j2html.rendering;
import java.io.IOException;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import org.xbib.j2html.utils.TextEscaper; import org.xbib.j2html.utils.TextEscaper;
import java.io.IOException;
/** /**
* Composes HTML without any extra line breaks or indentation. * Composes HTML without any extra line breaks or indentation.
* *
@ -50,6 +49,7 @@ public class FlatHtml<T extends Appendable> implements HtmlBuilder<T> {
/** /**
* Returns an HtmlBuilder that will generate flat HTML in memory * Returns an HtmlBuilder that will generate flat HTML in memory
* using the given Config. * using the given Config.
*
* @param config The Config which will specify text escapement, tag closing, etc. * @param config The Config which will specify text escapement, tag closing, etc.
* @return An HtmlBuilder for flat HTML. * @return An HtmlBuilder for flat HTML.
*/ */

View file

@ -1,12 +1,11 @@
package org.xbib.j2html.rendering; package org.xbib.j2html.rendering;
import org.xbib.j2html.Config;
import org.xbib.j2html.utils.Indenter;
import org.xbib.j2html.utils.TextEscaper;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import org.xbib.j2html.Config;
import org.xbib.j2html.utils.Indenter;
import org.xbib.j2html.utils.TextEscaper;
/** /**
* Composes HTML with lines breaks and indentation between tags and text. * Composes HTML with lines breaks and indentation between tags and text.

View file

@ -1,16 +1,15 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.rendering.TagBuilder;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import org.xbib.j2html.rendering.IndentedHtml;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.xbib.j2html.Config;
import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import org.xbib.j2html.rendering.IndentedHtml;
import org.xbib.j2html.rendering.TagBuilder;
public class ContainerTag<T extends ContainerTag<T>> extends Tag<T> { public class ContainerTag<T extends ContainerTag<T>> extends Tag<T> {
@ -148,7 +147,7 @@ public class ContainerTag<T extends ContainerTag<T>> extends Tag<T> {
public String renderFormatted() { public String renderFormatted() {
try { try {
return render(IndentedHtml.into(new StringBuilder(), Config.global())).toString(); return render(IndentedHtml.into(new StringBuilder(), Config.global())).toString();
}catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e.getMessage(), e); throw new RuntimeException(e.getMessage(), e);
} }
} }
@ -157,17 +156,17 @@ public class ContainerTag<T extends ContainerTag<T>> extends Tag<T> {
public <A extends Appendable> A render(HtmlBuilder<A> builder, Object model) throws IOException { public <A extends Appendable> A render(HtmlBuilder<A> builder, Object model) throws IOException {
if (hasTagName()) { if (hasTagName()) {
TagBuilder tagBuilder = builder.appendStartTag(getTagName()); TagBuilder tagBuilder = builder.appendStartTag(getTagName());
for(Attribute attribute : getAttributes()){ for (Attribute attribute : getAttributes()) {
attribute.render(tagBuilder, model); attribute.render(tagBuilder, model);
} }
tagBuilder.completeTag(); tagBuilder.completeTag();
} }
for(DomContent child : children){ for (DomContent child : children) {
child.render(builder, model); child.render(builder, model);
} }
if(hasTagName()) { if (hasTagName()) {
builder.appendEndTag(getTagName()); builder.appendEndTag(getTagName());
} }

View file

@ -16,7 +16,7 @@ public class DomContentJoiner {
} else { } else {
throw new RuntimeException("You can only join DomContent and String objects"); throw new RuntimeException("You can only join DomContent and String objects");
} }
if (i < stringOrDomObjects.length-1) { if (i < stringOrDomObjects.length - 1) {
sb.append(delimiter); sb.append(delimiter);
} }
} }

View file

@ -1,12 +1,11 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import java.io.IOException;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import org.xbib.j2html.attributes.Attribute; import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.rendering.TagBuilder;
import org.xbib.j2html.rendering.FlatHtml; import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder; import org.xbib.j2html.rendering.HtmlBuilder;
import org.xbib.j2html.rendering.TagBuilder;
import java.io.IOException;
public class EmptyTag<T extends EmptyTag<T>> extends Tag<T> { public class EmptyTag<T extends EmptyTag<T>> extends Tag<T> {

View file

@ -1,41 +1,27 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import org.xbib.j2html.Config; import java.io.IOException;
import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.Scanner; import java.nio.charset.StandardCharsets;
import org.xbib.j2html.Config;
import static org.xbib.j2html.TagCreator.rawHtml; import static org.xbib.j2html.TagCreator.rawHtml;
import static org.xbib.j2html.TagCreator.script; import static org.xbib.j2html.TagCreator.script;
import static org.xbib.j2html.TagCreator.style; import static org.xbib.j2html.TagCreator.style;
public class InlineStaticResource { public class InlineStaticResource {
public static ContainerTag<? extends Tag<?>> get(String path, TargetFormat format) { public static ContainerTag<? extends Tag<?>> get(InputStream inputStream, TargetFormat format) {
String fileString = getFileAsString(path); try {
String fileString = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
return switch (format) { return switch (format) {
case CSS_MIN -> style().with(rawHtml(Config.cssMinifier.minify(fileString))); case CSS_MIN -> style().with(rawHtml(Config.cssMinifier.minify(fileString)));
case JS_MIN -> script().with(rawHtml(Config.jsMinifier.minify((fileString)))); case JS_MIN -> script().with(rawHtml(Config.jsMinifier.minify((fileString))));
case CSS -> style().with(rawHtml(fileString)); case CSS -> style().with(rawHtml(fileString));
case JS -> script().with(rawHtml(fileString)); case JS -> script().with(rawHtml(fileString));
}; };
} catch (IOException e) {
throw new RuntimeException(e);
} }
public static String getFileAsString(String path) {
try {
return streamToString(InlineStaticResource.class.getResourceAsStream(path));
} catch (Exception expected) {
try {
return streamToString(new FileInputStream(path));
} catch (Exception exception) {
throw new RuntimeException("Couldn't find file with path='" + path + "'");
}
}
}
private static String streamToString(InputStream inputStream) {
Scanner s = new Scanner(inputStream).useDelimiter("\\A");
return s.hasNext() ? s.next() : "";
} }
public enum TargetFormat {CSS_MIN, CSS, JS_MIN, JS} public enum TargetFormat {CSS_MIN, CSS, JS_MIN, JS}

View file

@ -1,11 +1,10 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder;
public interface Renderable { public interface Renderable {

View file

@ -1,9 +1,9 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import org.xbib.j2html.attributes.Attr;
import org.xbib.j2html.attributes.Attribute;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import org.xbib.j2html.attributes.Attr;
import org.xbib.j2html.attributes.Attribute;
public abstract class Tag<T extends Tag<T>> extends DomContent implements IInstance<T> { public abstract class Tag<T extends Tag<T>> extends DomContent implements IInstance<T> {
private final String tagName; private final String tagName;
@ -32,7 +32,7 @@ public abstract class Tag<T extends Tag<T>> extends DomContent implements IInsta
* @param name the attribute * @param name the attribute
* @param value the attribute value * @param value the attribute value
*/ */
boolean setAttribute(String name, String value) { public boolean setAttribute(String name, String value) {
if (value == null) { if (value == null) {
return attributes.add(new Attribute(name)); return attributes.add(new Attribute(name));
} }
@ -146,68 +146,132 @@ public abstract class Tag<T extends Tag<T>> extends DomContent implements IInsta
translate translate
*/ */
public T withAccesskey(String accesskey){ return attr(Attr.ACCESSKEY, accesskey); } public T withAccesskey(String accesskey) {
return attr(Attr.ACCESSKEY, accesskey);
}
public T withClass(String className) { return attr(Attr.CLASS, className); } public T withClass(String className) {
return attr(Attr.CLASS, className);
}
public T isContenteditable(){ return attr(Attr.CONTENTEDITABLE, "true"); } public T isContenteditable() {
return attr(Attr.CONTENTEDITABLE, "true");
}
public T withData(String dataAttr, String value) { return attr(Attr.DATA + "-" + dataAttr, value); } public T withData(String dataAttr, String value) {
return attr(Attr.DATA + "-" + dataAttr, value);
}
public T withDir(String dir) { return attr(Attr.DIR, dir); } public T withDir(String dir) {
return attr(Attr.DIR, dir);
}
public T isDraggable(){ return attr(Attr.DRAGGABLE, "true"); } public T isDraggable() {
return attr(Attr.DRAGGABLE, "true");
}
public T isHidden() { return attr(Attr.HIDDEN, null); } public T isHidden() {
return attr(Attr.HIDDEN, null);
}
public T withId(String id) { return attr(Attr.ID, id); } public T withId(String id) {
return attr(Attr.ID, id);
}
public T withIs(String element){ return attr(Attr.IS, element); } public T withIs(String element) {
return attr(Attr.IS, element);
}
public T withLang(String lang) { return attr(Attr.LANG, lang); } public T withLang(String lang) {
return attr(Attr.LANG, lang);
}
public T withSlot(String name){ return attr(Attr.SLOT, name); } public T withSlot(String name) {
return attr(Attr.SLOT, name);
}
public T isSpellcheck(){ return attr(Attr.SPELLCHECK, "true"); } public T isSpellcheck() {
return attr(Attr.SPELLCHECK, "true");
}
public T withStyle(String style) { return attr(Attr.STYLE, style); } public T withStyle(String style) {
return attr(Attr.STYLE, style);
}
public T withTabindex(int index){ return attr(Attr.TABINDEX, index); } public T withTabindex(int index) {
return attr(Attr.TABINDEX, index);
}
public T withTitle(String title) { return attr(Attr.TITLE, title); } public T withTitle(String title) {
return attr(Attr.TITLE, title);
}
public T isTranslate(){ return attr(Attr.TRANSLATE, "yes"); } public T isTranslate() {
return attr(Attr.TRANSLATE, "yes");
}
// ----- start of withCond$ATTR variants ----- // ----- start of withCond$ATTR variants -----
public T withCondAccessKey(boolean condition, String accesskey){ return condAttr(condition, Attr.ACCESSKEY, accesskey); } public T withCondAccessKey(boolean condition, String accesskey) {
return condAttr(condition, Attr.ACCESSKEY, accesskey);
}
public T withCondClass(boolean condition, String className) { return condAttr(condition, Attr.CLASS, className); } public T withCondClass(boolean condition, String className) {
return condAttr(condition, Attr.CLASS, className);
}
public T withCondContenteditable(boolean condition){ return attr(Attr.CONTENTEDITABLE, (condition)?"true":"false");} public T withCondContenteditable(boolean condition) {
return attr(Attr.CONTENTEDITABLE, (condition) ? "true" : "false");
}
public T withCondData(boolean condition, String dataAttr, String value) { return condAttr(condition, Attr.DATA + "-" + dataAttr, value); } public T withCondData(boolean condition, String dataAttr, String value) {
return condAttr(condition, Attr.DATA + "-" + dataAttr, value);
}
public T withCondDir(boolean condition, String dir) { return condAttr(condition, Attr.DIR, dir); } public T withCondDir(boolean condition, String dir) {
return condAttr(condition, Attr.DIR, dir);
}
public T withCondDraggable(boolean condition){ return attr(Attr.DRAGGABLE, (condition)?"true":"false"); } public T withCondDraggable(boolean condition) {
return attr(Attr.DRAGGABLE, (condition) ? "true" : "false");
}
public T withCondHidden(boolean condition) { return condAttr(condition, Attr.HIDDEN, null); } public T withCondHidden(boolean condition) {
return condAttr(condition, Attr.HIDDEN, null);
}
public T withCondId(boolean condition, String id) { return condAttr(condition, Attr.ID, id); } public T withCondId(boolean condition, String id) {
return condAttr(condition, Attr.ID, id);
}
public T withCondIs(boolean condition, String element){ return condAttr(condition, Attr.IS, element); } public T withCondIs(boolean condition, String element) {
return condAttr(condition, Attr.IS, element);
}
public T withCondLang(boolean condition, String lang) { return condAttr(condition, Attr.LANG, lang); } public T withCondLang(boolean condition, String lang) {
return condAttr(condition, Attr.LANG, lang);
}
public T withCondSlot(boolean condition, String name){ return condAttr(condition, Attr.SLOT, name); } public T withCondSlot(boolean condition, String name) {
return condAttr(condition, Attr.SLOT, name);
}
public T withCondSpellcheck(boolean condition){ return attr(Attr.SPELLCHECK, (condition)?"true":"false"); } public T withCondSpellcheck(boolean condition) {
return attr(Attr.SPELLCHECK, (condition) ? "true" : "false");
}
public T withCondStyle(boolean condition, String style) { return condAttr(condition, Attr.STYLE, style); } public T withCondStyle(boolean condition, String style) {
return condAttr(condition, Attr.STYLE, style);
}
public T withCondTabindex(boolean condition, int index){ return condAttr(condition, Attr.TABINDEX, index+""); } public T withCondTabindex(boolean condition, int index) {
return condAttr(condition, Attr.TABINDEX, index + "");
}
public T withCondTitle(boolean condition, String title) { return condAttr(condition, Attr.TITLE, title); } public T withCondTitle(boolean condition, String title) {
return condAttr(condition, Attr.TITLE, title);
}
public T withCondTranslate(boolean condition){ return attr(Attr.TRANSLATE, (condition)?"yes":"no"); } public T withCondTranslate(boolean condition) {
return attr(Attr.TRANSLATE, (condition) ? "yes" : "no");
}
} }

View file

@ -1,11 +1,10 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import java.io.IOException;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml; import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder; import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException;
public class Text extends DomContent { public class Text extends DomContent {
private final String text; private final String text;

View file

@ -1,11 +1,10 @@
package org.xbib.j2html.tags; package org.xbib.j2html.tags;
import java.io.IOException;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import org.xbib.j2html.rendering.FlatHtml; import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.rendering.HtmlBuilder; import org.xbib.j2html.rendering.HtmlBuilder;
import java.io.IOException;
public class UnescapedText extends DomContent { public class UnescapedText extends DomContent {
private final String text; private final String text;

View file

@ -246,7 +246,7 @@ class Selector {
j = i + 1; j = i + 1;
} }
} }
substr = contents.substring(j, contents.length()); substr = contents.substring(j);
if (!(substr.trim().equals(""))) { if (!(substr.trim().equals(""))) {
parts.add(substr); parts.add(substr);
} }
@ -303,7 +303,7 @@ class Property implements Comparable<Property> {
j = i + 1; j = i + 1;
} }
} }
substr = property.substring(j, property.length()); substr = property.substring(j);
if (!(substr.trim().equals(""))) { if (!(substr.trim().equals(""))) {
parts.add(substr); parts.add(substr);
} }
@ -312,7 +312,7 @@ class Property implements Comparable<Property> {
} }
String prop = parts.get(0).trim(); String prop = parts.get(0).trim();
if (!(prop.length() > 2 && prop.substring(0, 2).equals("--"))) { if (!(prop.length() > 2 && prop.startsWith("--"))) {
prop = prop.toLowerCase(); prop = prop.toLowerCase();
} }
this.property = prop; this.property = prop;
@ -419,7 +419,7 @@ class Property implements Comparable<Property> {
class Part { class Part {
private String contents; private String contents;
private String property; private final String property;
/** /**
* Create a new property by parsing the given string. * Create a new property by parsing the given string.

View file

@ -6,7 +6,7 @@ public class EscapeUtil {
if (s == null) { if (s == null) {
return null; return null;
} }
StringBuilder escapedText = new StringBuilder(s.length()+16); StringBuilder escapedText = new StringBuilder(s.length() + 16);
char currentChar; char currentChar;
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
currentChar = s.charAt(i); currentChar = s.charAt(i);
@ -32,5 +32,4 @@ public class EscapeUtil {
} }
return escapedText.toString(); return escapedText.toString();
} }
} }

View file

@ -0,0 +1,19 @@
module org.xbib.j2html.test {
requires transitive org.junit.jupiter.api;
requires org.hamcrest;
requires org.xbib.j2html;
exports org.xbib.j2html.test;
exports org.xbib.j2html.test.attributes;
exports org.xbib.j2html.test.comparison.j2html;
exports org.xbib.j2html.test.comparison.model;
exports org.xbib.j2html.test.model;
exports org.xbib.j2html.test.rendering;
exports org.xbib.j2html.test.tags;
opens org.xbib.j2html.test;
opens org.xbib.j2html.test.attributes;
opens org.xbib.j2html.test.comparison.j2html;
opens org.xbib.j2html.test.comparison.model;
opens org.xbib.j2html.test.model;
opens org.xbib.j2html.test.rendering;
opens org.xbib.j2html.test.tags;
}

View file

@ -1,47 +0,0 @@
package org.xbib.j2html.tags;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.xbib.j2html.TagCreator.fileAsEscapedString;
import static org.xbib.j2html.TagCreator.fileAsString;
import static org.xbib.j2html.TagCreator.scriptWithInlineFile_min;
import static org.xbib.j2html.TagCreator.styleWithInlineFile_min;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class InlineStaticResourceTest {
private static final String EOL = System.getProperty("line.separator"); // System independent End Of Line
@Test
public void testAllTags() throws Exception {
String expectedCss = "<style>body{background:#daa520;margin-bottom:10px;margin-left:10px;margin-right:10px;margin-top:10px}</style>";
String expectedJs = "<script>(function(){var test=5;var tast=10;var testTast=test+tast;console.log(testTast);})();</script>";
String expectedHtml = "<body>" + EOL + " Any content" + EOL + "</body>" + EOL;
String expectedEscapedHtml = "&lt;body&gt;" + EOL + " Any content" + EOL + "&lt;/body&gt;" + EOL;
String expectedAnyContent = "public class AnyContent {" + EOL + "}" + EOL;
// classpath files
assertThat(styleWithInlineFile_min("/test.css").render(), is(expectedCss));
assertThat(styleWithInlineFile_min("/test-without-trailing-semis.css").render(), is(expectedCss));
assertThat(scriptWithInlineFile_min("/test.js").render(), is(expectedJs));
assertThat(fileAsString("/test.html").render(), is(expectedHtml));
assertThat(fileAsEscapedString("/test.html").render(), is(expectedEscapedHtml));
assertThat(fileAsString("/AnyContent.java").render(), is(expectedAnyContent));
// files outside classpath
assertThat(styleWithInlineFile_min("src/test/resources/test.css").render(), is(expectedCss));
assertThat(scriptWithInlineFile_min("src/test/resources/test.js").render(), is(expectedJs));
assertThat(fileAsString("src/test/resources/test.html").render(), is(expectedHtml));
assertThat(fileAsEscapedString("src/test/resources/test.html").render(), is(expectedEscapedHtml));
assertThat(fileAsString("src/test/resources/AnyContent.java").render(), is(expectedAnyContent));
}
@Test
public void testExceptionForBadPath() {
Assertions.assertThrows(RuntimeException.class, () -> {
styleWithInlineFile_min("NOT A FILE");
});
}
}

View file

@ -1,19 +1,10 @@
package org.xbib.j2html; package org.xbib.j2html.test;
//import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
//import com.carrotsearch.junitbenchmarks.BenchmarkRule;
//import com.carrotsearch.junitbenchmarks.Clock;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.utils.EscapeUtil; import org.xbib.j2html.utils.EscapeUtil;
import org.apache.commons.lang3.StringEscapeUtils;
//@FixMethodOrder(MethodSorters.NAME_ASCENDING)
//@BenchmarkOptions(callgc = false, benchmarkRounds = 10000, warmupRounds = 200, concurrency = 2, clock = Clock.NANO_TIME)
public class PerformanceTest { public class PerformanceTest {
//@Rule
//public TestRule benchmarkRun = new BenchmarkRule();
private String shortTestString = "<body>\n" private String shortTestString = "<body>\n"
+ " <h1 class=\"example\">Heading!</h1>\n" + " <h1 class=\"example\">Heading!</h1>\n"
+ " <img src=\"img/hello.png\">\n" + " <img src=\"img/hello.png\">\n"
@ -34,22 +25,12 @@ public class PerformanceTest {
+ "</code></pre>"; + "</code></pre>";
@Test @Test
public void testSimpleEscaperShort() throws Exception { public void testSimpleEscaperShort() {
EscapeUtil.escape(shortTestString); EscapeUtil.escape(shortTestString);
} }
@Test @Test
public void testSimpleEscaperLong() throws Exception { public void testSimpleEscaperLong() {
EscapeUtil.escape(longTestString); EscapeUtil.escape(longTestString);
} }
@Test
public void testApacheEscaperShort() throws Exception {
StringEscapeUtils.escapeHtml4(shortTestString);
}
@Test
public void testApacheEscaperLong() throws Exception {
StringEscapeUtils.escapeHtml4(longTestString);
}
} }

View file

@ -1,14 +1,14 @@
package org.xbib.j2html; package org.xbib.j2html.test;
//import com.carrotsearch.junitbenchmarks.BenchmarkOptions; //import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
//import com.carrotsearch.junitbenchmarks.BenchmarkRule; //import com.carrotsearch.junitbenchmarks.BenchmarkRule;
//import com.carrotsearch.junitbenchmarks.Clock; //import com.carrotsearch.junitbenchmarks.Clock;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.model.BrowserTitle; import org.xbib.j2html.test.model.BrowserTitle;
import org.xbib.j2html.model.Button; import org.xbib.j2html.test.model.Button;
import org.xbib.j2html.model.ButtonModel; import org.xbib.j2html.test.model.ButtonModel;
import org.xbib.j2html.model.PageModel; import org.xbib.j2html.test.model.PageModel;
import org.xbib.j2html.model.TextTemplate; import org.xbib.j2html.test.model.TextTemplate;
import org.xbib.j2html.tags.DomContent; import org.xbib.j2html.tags.DomContent;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.xbib.j2html.TagCreator.attrs; import static org.xbib.j2html.TagCreator.attrs;

View file

@ -1,6 +1,7 @@
package org.xbib.j2html; package org.xbib.j2html.test;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.Config;
import org.xbib.j2html.utils.EscapeUtil; import org.xbib.j2html.utils.EscapeUtil;
import org.xbib.j2html.utils.TextEscaper; import org.xbib.j2html.utils.TextEscaper;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.attributes; package org.xbib.j2html.test.attributes;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View file

@ -1,5 +1,6 @@
package org.xbib.j2html.attributes; package org.xbib.j2html.test.attributes;
import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.tags.ContainerTag; import org.xbib.j2html.tags.ContainerTag;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;

View file

@ -1,6 +1,6 @@
package org.xbib.j2html.comparison; package org.xbib.j2html.test.comparison;
import org.xbib.j2html.comparison.model.Employee; import org.xbib.j2html.test.comparison.model.Employee;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.comparison; package org.xbib.j2html.test.comparison;
//import com.carrotsearch.junitbenchmarks.BenchmarkOptions; //import com.carrotsearch.junitbenchmarks.BenchmarkOptions;
//import com.carrotsearch.junitbenchmarks.BenchmarkRule; //import com.carrotsearch.junitbenchmarks.BenchmarkRule;

View file

@ -1,9 +1,9 @@
package org.xbib.j2html.comparison; package org.xbib.j2html.test.comparison;
import org.xbib.j2html.comparison.j2html.FiveHundredEmployees; import org.xbib.j2html.test.comparison.j2html.FiveHundredEmployees;
import org.xbib.j2html.comparison.j2html.HelloWorld; import org.xbib.j2html.test.comparison.j2html.HelloWorld;
import org.xbib.j2html.comparison.j2html.Macros; import org.xbib.j2html.test.comparison.j2html.Macros;
import org.xbib.j2html.comparison.j2html.MultiplicationTable; import org.xbib.j2html.test.comparison.j2html.MultiplicationTable;
public class TestJ2html { public class TestJ2html {

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.comparison; package org.xbib.j2html.test.comparison;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;

View file

@ -1,6 +1,6 @@
package org.xbib.j2html.comparison.j2html; package org.xbib.j2html.test.comparison.j2html;
import org.xbib.j2html.comparison.ComparisonData; import org.xbib.j2html.test.comparison.ComparisonData;
import org.xbib.j2html.tags.specialized.UlTag; import org.xbib.j2html.tags.specialized.UlTag;
import static org.xbib.j2html.TagCreator.each; import static org.xbib.j2html.TagCreator.each;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.comparison.j2html; package org.xbib.j2html.test.comparison.j2html;
import org.xbib.j2html.tags.specialized.HtmlTag; import org.xbib.j2html.tags.specialized.HtmlTag;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.comparison.j2html; package org.xbib.j2html.test.comparison.j2html;
import org.xbib.j2html.tags.ContainerTag; import org.xbib.j2html.tags.ContainerTag;
import org.xbib.j2html.tags.DomContent; import org.xbib.j2html.tags.DomContent;

View file

@ -1,6 +1,7 @@
package org.xbib.j2html.comparison.j2html; package org.xbib.j2html.test.comparison.j2html;
import org.xbib.j2html.comparison.ComparisonData; import org.xbib.j2html.TagCreator;
import org.xbib.j2html.test.comparison.ComparisonData;
import org.xbib.j2html.tags.ContainerTag; import org.xbib.j2html.tags.ContainerTag;
import static org.xbib.j2html.TagCreator.each; import static org.xbib.j2html.TagCreator.each;
import static org.xbib.j2html.TagCreator.table; import static org.xbib.j2html.TagCreator.table;
@ -12,7 +13,7 @@ public class MultiplicationTable {
public static ContainerTag tag = table( public static ContainerTag tag = table(
tbody( tbody(
each(ComparisonData.tableNumbers, i -> tr( TagCreator.each(ComparisonData.tableNumbers, i -> tr(
each(ComparisonData.tableNumbers, j -> td( each(ComparisonData.tableNumbers, j -> td(
String.valueOf(i * j) String.valueOf(i * j)
)) ))

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.comparison.model; package org.xbib.j2html.test.comparison.model;
public class Employee { public class Employee {
int id; int id;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
import java.io.IOException; import java.io.IOException;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
import org.xbib.j2html.tags.ContainerTag; import org.xbib.j2html.tags.ContainerTag;
import java.io.IOException; import java.io.IOException;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
public class ButtonModel { public class ButtonModel {
private String text; private String text;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
import org.xbib.j2html.attributes.Attribute; import org.xbib.j2html.attributes.Attribute;
import java.io.IOException; import java.io.IOException;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
public class PageModel { public class PageModel {

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
import org.xbib.j2html.rendering.HtmlBuilder; import org.xbib.j2html.rendering.HtmlBuilder;
import org.xbib.j2html.tags.DomContent; import org.xbib.j2html.tags.DomContent;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.model; package org.xbib.j2html.test.model;
import java.io.IOException; import java.io.IOException;

View file

@ -1,9 +1,11 @@
package org.xbib.j2html.rendering; package org.xbib.j2html.test.rendering;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import java.io.IOException; import java.io.IOException;
import org.xbib.j2html.rendering.FlatHtml;
import static org.xbib.j2html.TagCreator.div; import static org.xbib.j2html.TagCreator.div;
import static org.xbib.j2html.TagCreator.input; import static org.xbib.j2html.TagCreator.input;
@ -14,7 +16,7 @@ public class FlatHtmlTest {
@Test @Test
public void start_tags_contain_attributes() throws IOException { public void start_tags_contain_attributes() throws IOException {
assertThat( MatcherAssert.assertThat(
FlatHtml.inMemory().appendStartTag("abc") FlatHtml.inMemory().appendStartTag("abc")
.appendAttribute("x", "X") .appendAttribute("x", "X")
.appendBooleanAttribute("y") .appendBooleanAttribute("y")

View file

@ -1,9 +1,11 @@
package org.xbib.j2html.rendering; package org.xbib.j2html.test.rendering;
import org.hamcrest.MatcherAssert;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import java.io.IOException; import java.io.IOException;
import org.xbib.j2html.rendering.IndentedHtml;
import static org.xbib.j2html.TagCreator.*; import static org.xbib.j2html.TagCreator.*;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
@ -13,7 +15,7 @@ public class IndentedHtmlTest {
@Test @Test
public void unescaped_text_is_not_modified() throws Exception { public void unescaped_text_is_not_modified() throws Exception {
assertThat( MatcherAssert.assertThat(
IndentedHtml.inMemory().appendUnescapedText("<>&\"\'").output().toString(), IndentedHtml.inMemory().appendUnescapedText("<>&\"\'").output().toString(),
is("<>&\"\'\n") is("<>&\"\'\n")
); );

View file

@ -1,7 +1,8 @@
package org.xbib.j2html.rendering; package org.xbib.j2html.test.rendering;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.attributes.Attribute; import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.rendering.FlatHtml;
import org.xbib.j2html.tags.DomContent; import org.xbib.j2html.tags.DomContent;
import java.io.IOException; import java.io.IOException;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.xbib.j2html.TagCreator.*; import static org.xbib.j2html.TagCreator.*;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.attributes.Attr; import org.xbib.j2html.attributes.Attr;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.xbib.j2html.TagCreator.a; import static org.xbib.j2html.TagCreator.a;

View file

@ -1,6 +1,10 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.tags.DomContentJoiner;
import org.xbib.j2html.tags.UnescapedText;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -8,7 +12,7 @@ public class DomContentJoinerTest {
@Test @Test
public void testJoin() { public void testJoin() {
assertThat(DomContentJoiner.join(",", true, "a", "b", "c"), is(new UnescapedText("a,b,c"))); MatcherAssert.assertThat(DomContentJoiner.join(",", true, "a", "b", "c"), Matchers.is(new UnescapedText("a,b,c")));
assertThat(DomContentJoiner.join(",", false, "a", "b", "c"), is(new UnescapedText("a,b,c"))); assertThat(DomContentJoiner.join(",", false, "a", "b", "c"), is(new UnescapedText("a,b,c")));
} }
} }

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
class Employee { class Employee {
final int id; final int id;

View file

@ -0,0 +1,52 @@
package org.xbib.j2html.test.tags;
import java.io.InputStream;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.xbib.j2html.TagCreator.fileAsEscapedString;
import static org.xbib.j2html.TagCreator.fileAsString;
import static org.xbib.j2html.TagCreator.scriptWithInlineFile_min;
import static org.xbib.j2html.TagCreator.styleWithInlineFile_min;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class InlineStaticResourceTest {
private static final String EOL = System.lineSeparator();
@Test
public void testAllTags() throws Exception {
String expectedCss = "<style>body{background:#daa520;margin-bottom:10px;margin-left:10px;margin-right:10px;margin-top:10px}</style>";
String expectedJs = "<script>(function(){var test=5;var tast=10;var testTast=test+tast;console.log(testTast);})();</script>";
String expectedHtml = "<body>" + EOL + " Any content" + EOL + "</body>" + EOL;
String expectedEscapedHtml = "&lt;body&gt;" + EOL + " Any content" + EOL + "&lt;/body&gt;" + EOL;
String expectedAnyContent = "public class AnyContent {" + EOL + "}" + EOL;
try (InputStream inputStream = getClass().getResourceAsStream("test.css")) {
assertThat(styleWithInlineFile_min(inputStream).render(), is(expectedCss));
}
try (InputStream inputStream = getClass().getResourceAsStream("test-without-trailing-semis.css")) {
assertThat(styleWithInlineFile_min(inputStream).render(), is(expectedCss));
}
try (InputStream inputStream = getClass().getResourceAsStream("test.js")) {
assertThat(scriptWithInlineFile_min(inputStream).render(), is(expectedJs));
}
try (InputStream inputStream = getClass().getResourceAsStream("test.html")) {
assertThat(fileAsString(inputStream).render(), is(expectedHtml));
}
try (InputStream inputStream = getClass().getResourceAsStream("test.html")) {
assertThat(fileAsEscapedString(inputStream).render(), is(expectedEscapedHtml));
}
try (InputStream inputStream = getClass().getResourceAsStream("AnyContent.java")) {
assertThat(fileAsString(inputStream).render(), is(expectedAnyContent));
}
}
@Test
public void testExceptionForBadPath() {
Assertions.assertThrows(RuntimeException.class, () -> {
styleWithInlineFile_min(null);
});
}
}

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.xbib.j2html.TagCreator.div; import static org.xbib.j2html.TagCreator.div;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.xbib.j2html.tags.DomContent;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.xbib.j2html.TagCreator.*; import static org.xbib.j2html.TagCreator.*;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;

View file

@ -1,11 +1,14 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.Config; import org.xbib.j2html.Config;
import org.xbib.j2html.attributes.Attribute; import org.xbib.j2html.attributes.Attribute;
import org.xbib.j2html.model.DynamicHrefAttribute; import org.xbib.j2html.test.model.DynamicHrefAttribute;
import org.xbib.j2html.tags.ContainerTag;
import org.xbib.j2html.tags.EmptyTag;
import org.xbib.j2html.tags.Tag;
import org.xbib.j2html.tags.specialized.HtmlTag; import org.xbib.j2html.tags.specialized.HtmlTag;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;

View file

@ -1,4 +1,4 @@
package org.xbib.j2html.tags; package org.xbib.j2html.test.tags;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.xbib.j2html.rendering.FlatHtml; import org.xbib.j2html.rendering.FlatHtml;

View file

@ -0,0 +1,4 @@
handlers=java.util.logging.ConsoleHandler
.level=ALL
java.util.logging.ConsoleHandler.level=ALL
jdk.event.security.level=INFO

View file

@ -0,0 +1,4 @@
module org.xbib.javapoet {
requires java.compiler;
exports org.xbib.javapoet;
}

View file

@ -0,0 +1,278 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import static org.xbib.javapoet.Util.characterLiteralWithoutSingleQuotes;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
public final class AnnotationSpec {
public static final String VALUE = "value";
public final TypeName type;
public final Map<String, List<CodeBlock>> members;
private AnnotationSpec(Builder builder) {
this.type = builder.type;
this.members = Util.immutableMultimap(builder.members);
}
void emit(CodeWriter codeWriter, boolean inline) throws IOException {
String whitespace = inline ? "" : "\n";
String memberSeparator = inline ? ", " : ",\n";
if (members.isEmpty()) {
// @Singleton
codeWriter.emit("@$T", type);
} else if (members.size() == 1 && members.containsKey("value")) {
// @Named("foo")
codeWriter.emit("@$T(", type);
emitAnnotationValues(codeWriter, whitespace, memberSeparator, members.get("value"));
codeWriter.emit(")");
} else {
// Inline:
// @Column(name = "updated_at", nullable = false)
//
// Not inline:
// @Column(
// name = "updated_at",
// nullable = false
// )
codeWriter.emit("@$T(" + whitespace, type);
codeWriter.indent(2);
for (Iterator<Map.Entry<String, List<CodeBlock>>> i
= members.entrySet().iterator(); i.hasNext(); ) {
Map.Entry<String, List<CodeBlock>> entry = i.next();
codeWriter.emit("$L = ", entry.getKey());
emitAnnotationValues(codeWriter, whitespace, memberSeparator, entry.getValue());
if (i.hasNext()) codeWriter.emit(memberSeparator);
}
codeWriter.unindent(2);
codeWriter.emit(whitespace + ")");
}
}
private void emitAnnotationValues(CodeWriter codeWriter, String whitespace,
String memberSeparator, List<CodeBlock> values) throws IOException {
if (values.size() == 1) {
codeWriter.indent(2);
codeWriter.emit(values.get(0));
codeWriter.unindent(2);
return;
}
codeWriter.emit("{" + whitespace);
codeWriter.indent(2);
boolean first = true;
for (CodeBlock codeBlock : values) {
if (!first) codeWriter.emit(memberSeparator);
codeWriter.emit(codeBlock);
first = false;
}
codeWriter.unindent(2);
codeWriter.emit(whitespace + "}");
}
public static AnnotationSpec get(Annotation annotation) {
return get(annotation, false);
}
public static AnnotationSpec get(Annotation annotation, boolean includeDefaultValues) {
Builder builder = builder(annotation.annotationType());
try {
Method[] methods = annotation.annotationType().getDeclaredMethods();
Arrays.sort(methods, Comparator.comparing(Method::getName));
for (Method method : methods) {
Object value = method.invoke(annotation);
if (!includeDefaultValues) {
if (Objects.deepEquals(value, method.getDefaultValue())) {
continue;
}
}
if (value.getClass().isArray()) {
for (int i = 0; i < Array.getLength(value); i++) {
builder.addMemberForValue(method.getName(), Array.get(value, i));
}
continue;
}
if (value instanceof Annotation) {
builder.addMember(method.getName(), "$L", get((Annotation) value));
continue;
}
builder.addMemberForValue(method.getName(), value);
}
} catch (Exception e) {
throw new RuntimeException("Reflecting " + annotation + " failed!", e);
}
return builder.build();
}
public static AnnotationSpec get(AnnotationMirror annotation) {
TypeElement element = (TypeElement) annotation.getAnnotationType().asElement();
AnnotationSpec.Builder builder = AnnotationSpec.builder(ClassName.get(element));
Visitor visitor = new Visitor(builder);
for (ExecutableElement executableElement : annotation.getElementValues().keySet()) {
String name = executableElement.getSimpleName().toString();
AnnotationValue value = annotation.getElementValues().get(executableElement);
value.accept(visitor, name);
}
return builder.build();
}
public static Builder builder(ClassName type) {
checkNotNull(type, "type == null");
return new Builder(type);
}
public static Builder builder(Class<?> type) {
return builder(ClassName.get(type));
}
public Builder toBuilder() {
Builder builder = new Builder(type);
for (Map.Entry<String, List<CodeBlock>> entry : members.entrySet()) {
builder.members.put(entry.getKey(), new ArrayList<>(entry.getValue()));
}
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
CodeWriter codeWriter = new CodeWriter(out);
codeWriter.emit("$L", this);
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public static final class Builder {
private final TypeName type;
public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
private Builder(TypeName type) {
this.type = type;
}
public Builder addMember(String name, String format, Object... args) {
return addMember(name, CodeBlock.of(format, args));
}
public Builder addMember(String name, CodeBlock codeBlock) {
List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
values.add(codeBlock);
return this;
}
/**
* Delegates to {@link #addMember(String, String, Object...)}, with parameter {@code format}
* depending on the given {@code value} object. Falls back to {@code "$L"} literal format if
* the class of the given {@code value} object is not supported.
*/
Builder addMemberForValue(String memberName, Object value) {
checkNotNull(memberName, "memberName == null");
checkNotNull(value, "value == null, constant non-null value expected for %s", memberName);
checkArgument(SourceVersion.isName(memberName), "not a valid name: %s", memberName);
if (value instanceof Class<?>) {
return addMember(memberName, "$T.class", value);
}
if (value instanceof Enum) {
return addMember(memberName, "$T.$L", value.getClass(), ((Enum<?>) value).name());
}
if (value instanceof String) {
return addMember(memberName, "$S", value);
}
if (value instanceof Float) {
return addMember(memberName, "$Lf", value);
}
if (value instanceof Long) {
return addMember(memberName, "$LL", value);
}
if (value instanceof Character) {
return addMember(memberName, "'$L'", characterLiteralWithoutSingleQuotes((char) value));
}
return addMember(memberName, "$L", value);
}
public AnnotationSpec build() {
for (String name : members.keySet()) {
checkNotNull(name, "name == null");
checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
}
return new AnnotationSpec(this);
}
}
/**
* Annotation value visitor adding members to the given builder instance.
*/
private static class Visitor extends SimpleAnnotationValueVisitor8<Builder, String> {
final Builder builder;
Visitor(Builder builder) {
super(builder);
this.builder = builder;
}
@Override
protected Builder defaultAction(Object o, String name) {
return builder.addMemberForValue(name, o);
}
@Override
public Builder visitAnnotation(AnnotationMirror a, String name) {
return builder.addMember(name, "$L", get(a));
}
@Override
public Builder visitEnumConstant(VariableElement c, String name) {
return builder.addMember(name, "$T.$L", c.asType(), c.getSimpleName());
}
@Override
public Builder visitType(TypeMirror t, String name) {
return builder.addMember(name, "$T.class", t);
}
@Override
public Builder visitArray(List<? extends AnnotationValue> values, String name) {
for (AnnotationValue value : values) {
value.accept(this, name);
}
return builder;
}
}
}

View file

@ -0,0 +1,104 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.ArrayType;
import static org.xbib.javapoet.Util.checkNotNull;
public final class ArrayTypeName extends TypeName {
public final TypeName componentType;
private ArrayTypeName(TypeName componentType) {
this(componentType, new ArrayList<>());
}
private ArrayTypeName(TypeName componentType, List<AnnotationSpec> annotations) {
super(annotations);
this.componentType = checkNotNull(componentType, "rawType == null");
}
@Override
public ArrayTypeName annotated(List<AnnotationSpec> annotations) {
return new ArrayTypeName(componentType, concatAnnotations(annotations));
}
@Override
public TypeName withoutAnnotations() {
return new ArrayTypeName(componentType);
}
@Override
CodeWriter emit(CodeWriter out) throws IOException {
return emit(out, false);
}
CodeWriter emit(CodeWriter out, boolean varargs) throws IOException {
emitLeafType(out);
return emitBrackets(out, varargs);
}
private CodeWriter emitLeafType(CodeWriter out) throws IOException {
if (TypeName.asArray(componentType) != null) {
return TypeName.asArray(componentType).emitLeafType(out);
}
return componentType.emit(out);
}
private CodeWriter emitBrackets(CodeWriter out, boolean varargs) throws IOException {
if (isAnnotated()) {
out.emit(" ");
emitAnnotations(out);
}
if (TypeName.asArray(componentType) == null) {
// Last bracket.
return out.emit(varargs ? "..." : "[]");
}
out.emit("[]");
return TypeName.asArray(componentType).emitBrackets(out, varargs);
}
/**
* Returns an array type whose elements are all instances of {@code componentType}.
*/
public static ArrayTypeName of(TypeName componentType) {
return new ArrayTypeName(componentType);
}
/**
* Returns an array type whose elements are all instances of {@code componentType}.
*/
public static ArrayTypeName of(Type componentType) {
return of(TypeName.get(componentType));
}
/**
* Returns an array type equivalent to {@code mirror}.
*/
public static ArrayTypeName get(ArrayType mirror) {
return get(mirror, new LinkedHashMap<>());
}
static ArrayTypeName get(
ArrayType mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
return new ArrayTypeName(get(mirror.getComponentType(), typeVariables));
}
/**
* Returns an array type equivalent to {@code type}.
*/
public static ArrayTypeName get(GenericArrayType type) {
return get(type, new LinkedHashMap<>());
}
static ArrayTypeName get(GenericArrayType type, Map<Type, TypeVariableName> map) {
return ArrayTypeName.of(get(type.getGenericComponentType(), map));
}
}

View file

@ -0,0 +1,314 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.lang.model.element.Element;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.SimpleElementVisitor8;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
/**
* A fully-qualified class name for top-level and member classes.
*/
public final class ClassName extends TypeName implements Comparable<ClassName> {
public static final ClassName OBJECT = ClassName.get(Object.class);
/**
* The name representing the default Java package.
*/
private static final String NO_PACKAGE = "";
/**
* The package name of this class, or "" if this is in the default package.
*/
final String packageName;
/**
* The enclosing class, or null if this is not enclosed in another class.
*/
final ClassName enclosingClassName;
/**
* This class name, like "Entry" for java.util.Map.Entry.
*/
final String simpleName;
private List<String> simpleNames;
/**
* The full class name like "java.util.Map.Entry".
*/
final String canonicalName;
private ClassName(String packageName, ClassName enclosingClassName, String simpleName) {
this(packageName, enclosingClassName, simpleName, Collections.emptyList());
}
private ClassName(String packageName, ClassName enclosingClassName, String simpleName,
List<AnnotationSpec> annotations) {
super(annotations);
this.packageName = Objects.requireNonNull(packageName, "packageName == null");
this.enclosingClassName = enclosingClassName;
this.simpleName = simpleName;
this.canonicalName = enclosingClassName != null
? (enclosingClassName.canonicalName + '.' + simpleName)
: (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
}
@Override
public ClassName annotated(List<AnnotationSpec> annotations) {
return new ClassName(packageName, enclosingClassName, simpleName,
concatAnnotations(annotations));
}
@Override
public ClassName withoutAnnotations() {
if (!isAnnotated()) return this;
ClassName resultEnclosingClassName = enclosingClassName != null
? enclosingClassName.withoutAnnotations()
: null;
return new ClassName(packageName, resultEnclosingClassName, simpleName);
}
@Override
public boolean isAnnotated() {
return super.isAnnotated() || (enclosingClassName != null && enclosingClassName.isAnnotated());
}
/**
* Returns the package name, like {@code "java.util"} for {@code Map.Entry}. Returns the empty
* string for the default package.
*/
public String packageName() {
return packageName;
}
/**
* Returns the enclosing class, like {@link Map} for {@code Map.Entry}. Returns null if this class
* is not nested in another class.
*/
public ClassName enclosingClassName() {
return enclosingClassName;
}
/**
* Returns the top class in this nesting group. Equivalent to chained calls to {@link
* #enclosingClassName()} until the result's enclosing class is null.
*/
public ClassName topLevelClassName() {
return enclosingClassName != null ? enclosingClassName.topLevelClassName() : this;
}
/**
* Return the binary name of a class.
*/
public String reflectionName() {
return enclosingClassName != null
? (enclosingClassName.reflectionName() + '$' + simpleName)
: (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
}
public List<String> simpleNames() {
if (simpleNames != null) {
return simpleNames;
}
if (enclosingClassName == null) {
simpleNames = Collections.singletonList(simpleName);
} else {
List<String> mutableNames = new ArrayList<>();
mutableNames.addAll(enclosingClassName().simpleNames());
mutableNames.add(simpleName);
simpleNames = Collections.unmodifiableList(mutableNames);
}
return simpleNames;
}
/**
* Returns a class that shares the same enclosing package or class. If this class is enclosed by
* another class, this is equivalent to {@code enclosingClassName().nestedClass(name)}. Otherwise
* it is equivalent to {@code get(packageName(), name)}.
*/
public ClassName peerClass(String name) {
return new ClassName(packageName, enclosingClassName, name);
}
/**
* Returns a new {@link ClassName} instance for the specified {@code name} as nested inside this
* class.
*/
public ClassName nestedClass(String name) {
return new ClassName(packageName, this, name);
}
/**
* Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}.
*/
public String simpleName() {
return simpleName;
}
/**
* Returns the full class name of this class.
* Like {@code "java.util.Map.Entry"} for {@link Map.Entry}.
*/
public String canonicalName() {
return canonicalName;
}
public static ClassName get(Class<?> clazz) {
checkNotNull(clazz, "clazz == null");
checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
String anonymousSuffix = "";
while (clazz.isAnonymousClass()) {
int lastDollar = clazz.getName().lastIndexOf('$');
anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix;
clazz = clazz.getEnclosingClass();
}
String name = clazz.getSimpleName() + anonymousSuffix;
if (clazz.getEnclosingClass() == null) {
// Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
int lastDot = clazz.getName().lastIndexOf('.');
String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : NO_PACKAGE;
return new ClassName(packageName, null, name);
}
return ClassName.get(clazz.getEnclosingClass()).nestedClass(name);
}
/**
* Returns a new {@link ClassName} instance for the given fully-qualified class name string. This
* method assumes that the input is ASCII and follows typical Java style (lowercase package
* names, UpperCamelCase class names) and may produce incorrect results or throw
* {@link IllegalArgumentException} otherwise. For that reason, {@link #get(Class)} and
* {@link #get(Class)} should be preferred as they can correctly create {@link ClassName}
* instances without such restrictions.
*/
public static ClassName bestGuess(String classNameString) {
// Add the package name, like "java.util.concurrent", or "" for no package.
int p = 0;
while (p < classNameString.length() && Character.isLowerCase(classNameString.codePointAt(p))) {
p = classNameString.indexOf('.', p) + 1;
checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
}
String packageName = p == 0 ? NO_PACKAGE : classNameString.substring(0, p - 1);
// Add class names like "Map" and "Entry".
ClassName className = null;
for (String simpleName : classNameString.substring(p).split("\\.", -1)) {
checkArgument(!simpleName.isEmpty() && Character.isUpperCase(simpleName.codePointAt(0)),
"couldn't make a guess for %s", classNameString);
className = new ClassName(packageName, className, simpleName);
}
return className;
}
/**
* Returns a class name created from the given parts. For example, calling this with package name
* {@code "java.util"} and simple names {@code "Map"}, {@code "Entry"} yields {@link Map.Entry}.
*/
public static ClassName get(String packageName, String simpleName, String... simpleNames) {
ClassName className = new ClassName(packageName, null, simpleName);
for (String name : simpleNames) {
className = className.nestedClass(name);
}
return className;
}
/**
* Returns the class name for {@code element}.
*/
public static ClassName get(TypeElement element) {
checkNotNull(element, "element == null");
String simpleName = element.getSimpleName().toString();
return element.getEnclosingElement().accept(new SimpleElementVisitor8<ClassName, Void>() {
@Override
public ClassName visitPackage(PackageElement packageElement, Void p) {
return new ClassName(packageElement.getQualifiedName().toString(), null, simpleName);
}
@Override
public ClassName visitType(TypeElement enclosingClass, Void p) {
return ClassName.get(enclosingClass).nestedClass(simpleName);
}
@Override
public ClassName visitUnknown(Element unknown, Void p) {
return get("", simpleName);
}
@Override
public ClassName defaultAction(Element enclosingElement, Void p) {
throw new IllegalArgumentException("Unexpected type nesting: " + element);
}
}, null);
}
@Override
public int compareTo(ClassName o) {
return canonicalName.compareTo(o.canonicalName);
}
@Override
CodeWriter emit(CodeWriter out) throws IOException {
boolean charsEmitted = false;
for (ClassName className : enclosingClasses()) {
String simpleName;
if (charsEmitted) {
// We've already emitted an enclosing class. Emit as we go.
out.emit(".");
simpleName = className.simpleName;
} else if (className.isAnnotated() || className == this) {
// We encountered the first enclosing class that must be emitted.
String qualifiedName = out.lookupName(className);
int dot = qualifiedName.lastIndexOf('.');
if (dot != -1) {
out.emitAndIndent(qualifiedName.substring(0, dot + 1));
simpleName = qualifiedName.substring(dot + 1);
charsEmitted = true;
} else {
simpleName = qualifiedName;
}
} else {
// Don't emit this enclosing type. Keep going so we can be more precise.
continue;
}
if (className.isAnnotated()) {
if (charsEmitted) out.emit(" ");
className.emitAnnotations(out);
}
out.emit(simpleName);
charsEmitted = true;
}
return out;
}
/**
* Returns all enclosing classes in this, outermost first.
*/
private List<ClassName> enclosingClasses() {
List<ClassName> result = new ArrayList<>();
for (ClassName c = this; c != null; c = c.enclosingClassName) {
result.add(c);
}
Collections.reverse(result);
return result;
}
}

View file

@ -0,0 +1,459 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collector;
import java.util.stream.StreamSupport;
import javax.lang.model.element.Element;
import javax.lang.model.type.TypeMirror;
import static org.xbib.javapoet.Util.checkArgument;
/**
* A fragment of a .java file, potentially containing declarations, statements, and documentation.
* Code blocks are not necessarily well-formed Java code, and are not validated. This class assumes
* javac will check correctness later!
*
* <p>Code blocks support placeholders like {@link java.text.Format}. Where {@link String#format}
* uses percent {@code %} to reference target values, this class uses dollar sign {@code $} and has
* its own set of permitted placeholders:
*
* <ul>
* <li>{@code $L} emits a <em>literal</em> value with no escaping. Arguments for literals may be
* strings, primitives, {@linkplain TypeSpec type declarations}, {@linkplain AnnotationSpec
* annotations} and even other code blocks.
* <li>{@code $N} emits a <em>name</em>, using name collision avoidance where necessary. Arguments
* for names may be strings (actually any {@linkplain CharSequence character sequence}),
* {@linkplain ParameterSpec parameters}, {@linkplain FieldSpec fields}, {@linkplain
* MethodSpec methods}, and {@linkplain TypeSpec types}.
* <li>{@code $S} escapes the value as a <em>string</em>, wraps it with double quotes, and emits
* that. For example, {@code 6" sandwich} is emitted {@code "6\" sandwich"}.
* <li>{@code $T} emits a <em>type</em> reference. Types will be imported if possible. Arguments
* for types may be {@linkplain Class classes}, {@linkplain javax.lang.model.type.TypeMirror
* ,* type mirrors}, and {@linkplain javax.lang.model.element.Element elements}.
* <li>{@code $$} emits a dollar sign.
* <li>{@code $W} emits a space or a newline, depending on its position on the line. This prefers
* to wrap lines before 100 columns.
* <li>{@code $Z} acts as a zero-width space. This prefers to wrap lines before 100 columns.
* <li>{@code $>} increases the indentation level.
* <li>{@code $<} decreases the indentation level.
* <li>{@code $[} begins a statement. For multiline statements, every line after the first line
* is double-indented.
* <li>{@code $]} ends a statement.
* </ul>
*/
public final class CodeBlock {
private static final Pattern NAMED_ARGUMENT =
Pattern.compile("\\$(?<argumentName>[\\w_]+):(?<typeChar>[\\w]).*");
private static final Pattern LOWERCASE = Pattern.compile("[a-z]+[\\w_]*");
/**
* A heterogeneous list containing string literals and value placeholders.
*/
final List<String> formatParts;
final List<Object> args;
private CodeBlock(Builder builder) {
this.formatParts = Util.immutableList(builder.formatParts);
this.args = Util.immutableList(builder.args);
}
public boolean isEmpty() {
return formatParts.isEmpty();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
new CodeWriter(out).emit(this);
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public static CodeBlock of(String format, Object... args) {
return new Builder().add(format, args).build();
}
/**
* Joins {@code codeBlocks} into a single {@link CodeBlock}, each separated by {@code separator}.
* For example, joining {@code String s}, {@code Object o} and {@code int i} using {@code ", "}
* would produce {@code String s, Object o, int i}.
*/
public static CodeBlock join(Iterable<CodeBlock> codeBlocks, String separator) {
return StreamSupport.stream(codeBlocks.spliterator(), false).collect(joining(separator));
}
/**
* A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
* separated by {@code separator}. For example, joining {@code String s}, {@code Object o} and
* {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
*/
public static Collector<CodeBlock, ?, CodeBlock> joining(String separator) {
return Collector.of(
() -> new CodeBlockJoiner(separator, builder()),
CodeBlockJoiner::add,
CodeBlockJoiner::merge,
CodeBlockJoiner::join);
}
/**
* A {@link Collector} implementation that joins {@link CodeBlock} instances together into one
* separated by {@code separator}. For example, joining {@code String s}, {@code Object o} and
* {@code int i} using {@code ", "} would produce {@code String s, Object o, int i}.
*/
public static Collector<CodeBlock, ?, CodeBlock> joining(
String separator, String prefix, String suffix) {
Builder builder = builder().add("$N", prefix);
return Collector.of(
() -> new CodeBlockJoiner(separator, builder),
CodeBlockJoiner::add,
CodeBlockJoiner::merge,
joiner -> {
builder.add(CodeBlock.of("$N", suffix));
return joiner.join();
});
}
public static Builder builder() {
return new Builder();
}
public Builder toBuilder() {
Builder builder = new Builder();
builder.formatParts.addAll(formatParts);
builder.args.addAll(args);
return builder;
}
public static final class Builder {
final List<String> formatParts = new ArrayList<>();
final List<Object> args = new ArrayList<>();
private Builder() {
}
public boolean isEmpty() {
return formatParts.isEmpty();
}
/**
* Adds code using named arguments.
*
* <p>Named arguments specify their name after the '$' followed by : and the corresponding type
* character. Argument names consist of characters in {@code a-z, A-Z, 0-9, and _} and must
* start with a lowercase character.
*
* <p>For example, to refer to the type {@link java.lang.Integer} with the argument name {@code
* clazz} use a format string containing {@code $clazz:T} and include the key {@code clazz} with
* value {@code java.lang.Integer.class} in the argument map.
*/
public Builder addNamed(String format, Map<String, ?> arguments) {
int p = 0;
for (String argument : arguments.keySet()) {
checkArgument(LOWERCASE.matcher(argument).matches(),
"argument '%s' must start with a lowercase character", argument);
}
while (p < format.length()) {
int nextP = format.indexOf("$", p);
if (nextP == -1) {
formatParts.add(format.substring(p));
break;
}
if (p != nextP) {
formatParts.add(format.substring(p, nextP));
p = nextP;
}
Matcher matcher = null;
int colon = format.indexOf(':', p);
if (colon != -1) {
int endIndex = Math.min(colon + 2, format.length());
matcher = NAMED_ARGUMENT.matcher(format.substring(p, endIndex));
}
if (matcher != null && matcher.lookingAt()) {
String argumentName = matcher.group("argumentName");
checkArgument(arguments.containsKey(argumentName), "Missing named argument for $%s",
argumentName);
char formatChar = matcher.group("typeChar").charAt(0);
addArgument(format, formatChar, arguments.get(argumentName));
formatParts.add("$" + formatChar);
p += matcher.regionEnd();
} else {
checkArgument(p < format.length() - 1, "dangling $ at end");
checkArgument(isNoArgPlaceholder(format.charAt(p + 1)),
"unknown format $%s at %s in '%s'", format.charAt(p + 1), p + 1, format);
formatParts.add(format.substring(p, p + 2));
p += 2;
}
}
return this;
}
/**
* Add code with positional or relative arguments.
*
* <p>Relative arguments map 1:1 with the placeholders in the format string.
*
* <p>Positional arguments use an index after the placeholder to identify which argument index
* to use. For example, for a literal to reference the 3rd argument: "$3L" (1 based index)
*
* <p>Mixing relative and positional arguments in a call to add is invalid and will result in an
* error.
*/
public Builder add(String format, Object... args) {
boolean hasRelative = false;
boolean hasIndexed = false;
int relativeParameterCount = 0;
int[] indexedParameterCount = new int[args.length];
for (int p = 0; p < format.length(); ) {
if (format.charAt(p) != '$') {
int nextP = format.indexOf('$', p + 1);
if (nextP == -1) nextP = format.length();
formatParts.add(format.substring(p, nextP));
p = nextP;
continue;
}
p++; // '$'.
// Consume zero or more digits, leaving 'c' as the first non-digit char after the '$'.
int indexStart = p;
char c;
do {
checkArgument(p < format.length(), "dangling format characters in '%s'", format);
c = format.charAt(p++);
} while (c >= '0' && c <= '9');
int indexEnd = p - 1;
// If 'c' doesn't take an argument, we're done.
if (isNoArgPlaceholder(c)) {
checkArgument(
indexStart == indexEnd, "$$, $>, $<, $[, $], $W, and $Z may not have an index");
formatParts.add("$" + c);
continue;
}
// Find either the indexed argument, or the relative argument. (0-based).
int index;
if (indexStart < indexEnd) {
index = Integer.parseInt(format.substring(indexStart, indexEnd)) - 1;
hasIndexed = true;
if (args.length > 0) {
indexedParameterCount[index % args.length]++; // modulo is needed, checked below anyway
}
} else {
index = relativeParameterCount;
hasRelative = true;
relativeParameterCount++;
}
checkArgument(index >= 0 && index < args.length,
"index %d for '%s' not in range (received %s arguments)",
index + 1, format.substring(indexStart - 1, indexEnd + 1), args.length);
checkArgument(!hasIndexed || !hasRelative, "cannot mix indexed and positional parameters");
addArgument(format, c, args[index]);
formatParts.add("$" + c);
}
if (hasRelative) {
checkArgument(relativeParameterCount >= args.length,
"unused arguments: expected %s, received %s", relativeParameterCount, args.length);
}
if (hasIndexed) {
List<String> unused = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
if (indexedParameterCount[i] == 0) {
unused.add("$" + (i + 1));
}
}
String s = unused.size() == 1 ? "" : "s";
checkArgument(unused.isEmpty(), "unused argument%s: %s", s, String.join(", ", unused));
}
return this;
}
private boolean isNoArgPlaceholder(char c) {
return c == '$' || c == '>' || c == '<' || c == '[' || c == ']' || c == 'W' || c == 'Z';
}
private void addArgument(String format, char c, Object arg) {
switch (c) {
case 'N':
this.args.add(argToName(arg));
break;
case 'L':
this.args.add(argToLiteral(arg));
break;
case 'S':
this.args.add(argToString(arg));
break;
case 'T':
this.args.add(argToType(arg));
break;
default:
throw new IllegalArgumentException(
String.format("invalid format string: '%s'", format));
}
}
private String argToName(Object o) {
if (o instanceof CharSequence) return o.toString();
if (o instanceof ParameterSpec) return ((ParameterSpec) o).name;
if (o instanceof FieldSpec) return ((FieldSpec) o).name;
if (o instanceof MethodSpec) return ((MethodSpec) o).name;
if (o instanceof TypeSpec) return ((TypeSpec) o).name;
throw new IllegalArgumentException("expected name but was " + o);
}
private Object argToLiteral(Object o) {
return o;
}
private String argToString(Object o) {
return o != null ? String.valueOf(o) : null;
}
private TypeName argToType(Object o) {
if (o instanceof TypeName) return (TypeName) o;
if (o instanceof TypeMirror) return TypeName.get((TypeMirror) o);
if (o instanceof Element) return TypeName.get(((Element) o).asType());
if (o instanceof Type) return TypeName.get((Type) o);
throw new IllegalArgumentException("expected type but was " + o);
}
/**
* @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
* Shouldn't contain braces or newline characters.
*/
public Builder beginControlFlow(String controlFlow, Object... args) {
add(controlFlow + " {\n", args);
indent();
return this;
}
/**
* @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
* Shouldn't contain braces or newline characters.
*/
public Builder nextControlFlow(String controlFlow, Object... args) {
unindent();
add("} " + controlFlow + " {\n", args);
indent();
return this;
}
public Builder endControlFlow() {
unindent();
add("}\n");
return this;
}
/**
* @param controlFlow the optional control flow construct and its code, such as
* "while(foo == 20)". Only used for "do/while" control flows.
*/
public Builder endControlFlow(String controlFlow, Object... args) {
unindent();
add("} " + controlFlow + ";\n", args);
return this;
}
public Builder addStatement(String format, Object... args) {
add("$[");
add(format, args);
add(";\n$]");
return this;
}
public Builder addStatement(CodeBlock codeBlock) {
return addStatement("$L", codeBlock);
}
public Builder add(CodeBlock codeBlock) {
formatParts.addAll(codeBlock.formatParts);
args.addAll(codeBlock.args);
return this;
}
public Builder indent() {
this.formatParts.add("$>");
return this;
}
public Builder unindent() {
this.formatParts.add("$<");
return this;
}
public Builder clear() {
formatParts.clear();
args.clear();
return this;
}
public CodeBlock build() {
return new CodeBlock(this);
}
}
private static final class CodeBlockJoiner {
private final String delimiter;
private final Builder builder;
private boolean first = true;
CodeBlockJoiner(String delimiter, Builder builder) {
this.delimiter = delimiter;
this.builder = builder;
}
CodeBlockJoiner add(CodeBlock codeBlock) {
if (!first) {
builder.add(delimiter);
}
first = false;
builder.add(codeBlock);
return this;
}
CodeBlockJoiner merge(CodeBlockJoiner other) {
CodeBlock otherBlock = other.builder.build();
if (!otherBlock.isEmpty()) {
add(otherBlock);
}
return this;
}
CodeBlock join() {
return builder.build();
}
}
}

View file

@ -0,0 +1,535 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.regex.Pattern;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import static java.lang.String.join;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
import static org.xbib.javapoet.Util.checkState;
import static org.xbib.javapoet.Util.stringLiteralWithDoubleQuotes;
/**
* Converts a {@link JavaFile} to a string suitable to both human- and javac-consumption. This
* honors imports, indentation, and deferred variable names.
*/
final class CodeWriter {
/**
* Sentinel value that indicates that no user-provided package has been set.
*/
private static final String NO_PACKAGE = new String();
private static final Pattern LINE_BREAKING_PATTERN = Pattern.compile("\\R");
private final String indent;
private final LineWrapper out;
private int indentLevel;
private boolean javadoc = false;
private boolean comment = false;
private String packageName = NO_PACKAGE;
private final List<TypeSpec> typeSpecStack = new ArrayList<>();
private final Set<String> staticImportClassNames;
private final Set<String> staticImports;
private final Set<String> alwaysQualify;
private final Map<String, ClassName> importedTypes;
private final Map<String, ClassName> importableTypes = new LinkedHashMap<>();
private final Set<String> referencedNames = new LinkedHashSet<>();
private final Multiset<String> currentTypeVariables = new Multiset<>();
private boolean trailingNewline;
/**
* When emitting a statement, this is the line of the statement currently being written. The first
* line of a statement is indented normally and subsequent wrapped lines are double-indented. This
* is -1 when the currently-written line isn't part of a statement.
*/
int statementLine = -1;
CodeWriter(Appendable out) {
this(out, " ", Collections.emptySet(), Collections.emptySet());
}
CodeWriter(Appendable out, String indent, Set<String> staticImports, Set<String> alwaysQualify) {
this(out, indent, Collections.emptyMap(), staticImports, alwaysQualify);
}
CodeWriter(Appendable out,
String indent,
Map<String, ClassName> importedTypes,
Set<String> staticImports,
Set<String> alwaysQualify) {
this.out = new LineWrapper(out, indent, 100);
this.indent = checkNotNull(indent, "indent == null");
this.importedTypes = checkNotNull(importedTypes, "importedTypes == null");
this.staticImports = checkNotNull(staticImports, "staticImports == null");
this.alwaysQualify = checkNotNull(alwaysQualify, "alwaysQualify == null");
this.staticImportClassNames = new LinkedHashSet<>();
for (String signature : staticImports) {
staticImportClassNames.add(signature.substring(0, signature.lastIndexOf('.')));
}
}
public Map<String, ClassName> importedTypes() {
return importedTypes;
}
public CodeWriter indent() {
return indent(1);
}
public CodeWriter indent(int levels) {
indentLevel += levels;
return this;
}
public CodeWriter unindent() {
return unindent(1);
}
public CodeWriter unindent(int levels) {
checkArgument(indentLevel - levels >= 0, "cannot unindent %s from %s", levels, indentLevel);
indentLevel -= levels;
return this;
}
public CodeWriter pushPackage(String packageName) {
checkState(this.packageName == NO_PACKAGE, "package already set: %s", this.packageName);
this.packageName = checkNotNull(packageName, "packageName == null");
return this;
}
public CodeWriter popPackage() {
checkState(this.packageName != NO_PACKAGE, "package not set");
this.packageName = NO_PACKAGE;
return this;
}
public CodeWriter pushType(TypeSpec type) {
this.typeSpecStack.add(type);
return this;
}
public CodeWriter popType() {
this.typeSpecStack.remove(typeSpecStack.size() - 1);
return this;
}
public void emitComment(CodeBlock codeBlock) throws IOException {
trailingNewline = true; // Force the '//' prefix for the comment.
comment = true;
try {
emit(codeBlock);
emit("\n");
} finally {
comment = false;
}
}
public void emitJavadoc(CodeBlock javadocCodeBlock) throws IOException {
if (javadocCodeBlock.isEmpty()) return;
emit("/**\n");
javadoc = true;
try {
emit(javadocCodeBlock, true);
} finally {
javadoc = false;
}
emit(" */\n");
}
public void emitAnnotations(List<AnnotationSpec> annotations, boolean inline) throws IOException {
for (AnnotationSpec annotationSpec : annotations) {
annotationSpec.emit(this, inline);
emit(inline ? " " : "\n");
}
}
/**
* Emits {@code modifiers} in the standard order. Modifiers in {@code implicitModifiers} will not
* be emitted.
*/
public void emitModifiers(Set<Modifier> modifiers, Set<Modifier> implicitModifiers)
throws IOException {
if (modifiers.isEmpty()) return;
for (Modifier modifier : EnumSet.copyOf(modifiers)) {
if (implicitModifiers.contains(modifier)) continue;
emitAndIndent(modifier.name().toLowerCase(Locale.US));
emitAndIndent(" ");
}
}
public void emitModifiers(Set<Modifier> modifiers) throws IOException {
emitModifiers(modifiers, Collections.emptySet());
}
/**
* Emit type variables with their bounds. This should only be used when declaring type variables;
* everywhere else bounds are omitted.
*/
public void emitTypeVariables(List<TypeVariableName> typeVariables) throws IOException {
if (typeVariables.isEmpty()) return;
typeVariables.forEach(typeVariable -> currentTypeVariables.add(typeVariable.name));
emit("<");
boolean firstTypeVariable = true;
for (TypeVariableName typeVariable : typeVariables) {
if (!firstTypeVariable) emit(", ");
emitAnnotations(typeVariable.annotations, true);
emit("$L", typeVariable.name);
boolean firstBound = true;
for (TypeName bound : typeVariable.bounds) {
emit(firstBound ? " extends $T" : " & $T", bound);
firstBound = false;
}
firstTypeVariable = false;
}
emit(">");
}
public void popTypeVariables(List<TypeVariableName> typeVariables) throws IOException {
typeVariables.forEach(typeVariable -> currentTypeVariables.remove(typeVariable.name));
}
public CodeWriter emit(String s) throws IOException {
return emitAndIndent(s);
}
public CodeWriter emit(String format, Object... args) throws IOException {
return emit(CodeBlock.of(format, args));
}
public CodeWriter emit(CodeBlock codeBlock) throws IOException {
return emit(codeBlock, false);
}
public CodeWriter emit(CodeBlock codeBlock, boolean ensureTrailingNewline) throws IOException {
int a = 0;
ClassName deferredTypeName = null; // used by "import static" logic
ListIterator<String> partIterator = codeBlock.formatParts.listIterator();
while (partIterator.hasNext()) {
String part = partIterator.next();
switch (part) {
case "$L":
emitLiteral(codeBlock.args.get(a++));
break;
case "$N":
emitAndIndent((String) codeBlock.args.get(a++));
break;
case "$S":
String string = (String) codeBlock.args.get(a++);
// Emit null as a literal null: no quotes.
emitAndIndent(string != null
? stringLiteralWithDoubleQuotes(string, indent)
: "null");
break;
case "$T":
TypeName typeName = (TypeName) codeBlock.args.get(a++);
// defer "typeName.emit(this)" if next format part will be handled by the default case
if (typeName instanceof ClassName && partIterator.hasNext()) {
if (!codeBlock.formatParts.get(partIterator.nextIndex()).startsWith("$")) {
ClassName candidate = (ClassName) typeName;
if (staticImportClassNames.contains(candidate.canonicalName)) {
checkState(deferredTypeName == null, "pending type for static import?!");
deferredTypeName = candidate;
break;
}
}
}
typeName.emit(this);
break;
case "$$":
emitAndIndent("$");
break;
case "$>":
indent();
break;
case "$<":
unindent();
break;
case "$[":
checkState(statementLine == -1, "statement enter $[ followed by statement enter $[");
statementLine = 0;
break;
case "$]":
checkState(statementLine != -1, "statement exit $] has no matching statement enter $[");
if (statementLine > 0) {
unindent(2); // End a multi-line statement. Decrease the indentation level.
}
statementLine = -1;
break;
case "$W":
out.wrappingSpace(indentLevel + 2);
break;
case "$Z":
out.zeroWidthSpace(indentLevel + 2);
break;
default:
// handle deferred type
if (deferredTypeName != null) {
if (part.startsWith(".")) {
if (emitStaticImportMember(deferredTypeName.canonicalName, part)) {
// okay, static import hit and all was emitted, so clean-up and jump to next part
deferredTypeName = null;
break;
}
}
deferredTypeName.emit(this);
deferredTypeName = null;
}
emitAndIndent(part);
break;
}
}
if (ensureTrailingNewline && out.lastChar() != '\n') {
emit("\n");
}
return this;
}
public CodeWriter emitWrappingSpace() throws IOException {
out.wrappingSpace(indentLevel + 2);
return this;
}
private static String extractMemberName(String part) {
checkArgument(Character.isJavaIdentifierStart(part.charAt(0)), "not an identifier: %s", part);
for (int i = 1; i <= part.length(); i++) {
if (!SourceVersion.isIdentifier(part.substring(0, i))) {
return part.substring(0, i - 1);
}
}
return part;
}
private boolean emitStaticImportMember(String canonical, String part) throws IOException {
String partWithoutLeadingDot = part.substring(1);
if (partWithoutLeadingDot.isEmpty()) return false;
char first = partWithoutLeadingDot.charAt(0);
if (!Character.isJavaIdentifierStart(first)) return false;
String explicit = canonical + "." + extractMemberName(partWithoutLeadingDot);
String wildcard = canonical + ".*";
if (staticImports.contains(explicit) || staticImports.contains(wildcard)) {
emitAndIndent(partWithoutLeadingDot);
return true;
}
return false;
}
private void emitLiteral(Object o) throws IOException {
if (o instanceof TypeSpec) {
TypeSpec typeSpec = (TypeSpec) o;
typeSpec.emit(this, null, Collections.emptySet());
} else if (o instanceof AnnotationSpec) {
AnnotationSpec annotationSpec = (AnnotationSpec) o;
annotationSpec.emit(this, true);
} else if (o instanceof CodeBlock) {
CodeBlock codeBlock = (CodeBlock) o;
emit(codeBlock);
} else {
emitAndIndent(String.valueOf(o));
}
}
/**
* Returns the best name to identify {@code className} with in the current context. This uses the
* available imports and the current scope to find the shortest name available. It does not honor
* names visible due to inheritance.
*/
String lookupName(ClassName className) {
// If the top level simple name is masked by a current type variable, use the canonical name.
String topLevelSimpleName = className.topLevelClassName().simpleName();
if (currentTypeVariables.contains(topLevelSimpleName)) {
return className.canonicalName;
}
// Find the shortest suffix of className that resolves to className. This uses both local type
// names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports.
boolean nameResolved = false;
for (ClassName c = className; c != null; c = c.enclosingClassName()) {
ClassName resolved = resolve(c.simpleName());
nameResolved = resolved != null;
if (resolved != null && Objects.equals(resolved.canonicalName, c.canonicalName)) {
int suffixOffset = c.simpleNames().size() - 1;
return join(".", className.simpleNames().subList(
suffixOffset, className.simpleNames().size()));
}
}
// If the name resolved but wasn't a match, we're stuck with the fully qualified name.
if (nameResolved) {
return className.canonicalName;
}
// If the class is in the same package, we're done.
if (Objects.equals(packageName, className.packageName())) {
referencedNames.add(topLevelSimpleName);
return join(".", className.simpleNames());
}
// We'll have to use the fully-qualified name. Mark the type as importable for a future pass.
if (!javadoc) {
importableType(className);
}
return className.canonicalName;
}
private void importableType(ClassName className) {
if (className.packageName().isEmpty()) {
return;
} else if (alwaysQualify.contains(className.simpleName)) {
// TODO what about nested types like java.util.Map.Entry?
return;
}
ClassName topLevelClassName = className.topLevelClassName();
String simpleName = topLevelClassName.simpleName();
ClassName replaced = importableTypes.put(simpleName, topLevelClassName);
if (replaced != null) {
importableTypes.put(simpleName, replaced); // On collision, prefer the first inserted.
}
}
/**
* Returns the class referenced by {@code simpleName}, using the current nesting context and
* imports.
*/
// TODO(jwilson): also honor superclass members when resolving names.
private ClassName resolve(String simpleName) {
// Match a child of the current (potentially nested) class.
for (int i = typeSpecStack.size() - 1; i >= 0; i--) {
TypeSpec typeSpec = typeSpecStack.get(i);
if (typeSpec.nestedTypesSimpleNames.contains(simpleName)) {
return stackClassName(i, simpleName);
}
}
// Match the top-level class.
if (typeSpecStack.size() > 0 && Objects.equals(typeSpecStack.get(0).name, simpleName)) {
return ClassName.get(packageName, simpleName);
}
// Match an imported type.
ClassName importedType = importedTypes.get(simpleName);
if (importedType != null) return importedType;
// No match.
return null;
}
/**
* Returns the class named {@code simpleName} when nested in the class at {@code stackDepth}.
*/
private ClassName stackClassName(int stackDepth, String simpleName) {
ClassName className = ClassName.get(packageName, typeSpecStack.get(0).name);
for (int i = 1; i <= stackDepth; i++) {
className = className.nestedClass(typeSpecStack.get(i).name);
}
return className.nestedClass(simpleName);
}
/**
* Emits {@code s} with indentation as required. It's important that all code that writes to
* {@link #out} does it through here, since we emit indentation lazily in order to avoid
* unnecessary trailing whitespace.
*/
CodeWriter emitAndIndent(String s) throws IOException {
boolean first = true;
for (String line : LINE_BREAKING_PATTERN.split(s, -1)) {
// Emit a newline character. Make sure blank lines in Javadoc & comments look good.
if (!first) {
if ((javadoc || comment) && trailingNewline) {
emitIndentation();
out.append(javadoc ? " *" : "//");
}
out.append("\n");
trailingNewline = true;
if (statementLine != -1) {
if (statementLine == 0) {
indent(2); // Begin multiple-line statement. Increase the indentation level.
}
statementLine++;
}
}
first = false;
if (line.isEmpty()) continue; // Don't indent empty lines.
// Emit indentation and comment prefix if necessary.
if (trailingNewline) {
emitIndentation();
if (javadoc) {
out.append(" * ");
} else if (comment) {
out.append("// ");
}
}
out.append(line);
trailingNewline = false;
}
return this;
}
private void emitIndentation() throws IOException {
for (int j = 0; j < indentLevel; j++) {
out.append(indent);
}
}
/**
* Returns the types that should have been imported for this code. If there were any simple name
* collisions, that type's first use is imported.
*/
Map<String, ClassName> suggestedImports() {
Map<String, ClassName> result = new LinkedHashMap<>(importableTypes);
result.keySet().removeAll(referencedNames);
return result;
}
// A makeshift multi-set implementation
private static final class Multiset<T> {
private final Map<T, Integer> map = new LinkedHashMap<>();
void add(T t) {
int count = map.getOrDefault(t, 0);
map.put(t, count + 1);
}
void remove(T t) {
int count = map.getOrDefault(t, 0);
if (count == 0) {
throw new IllegalStateException(t + " is not in the multiset");
}
map.put(t, count - 1);
}
boolean contains(T t) {
return map.getOrDefault(t, 0) > 0;
}
}
}

View file

@ -0,0 +1,164 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
import static org.xbib.javapoet.Util.checkState;
/**
* A generated field declaration.
*/
public final class FieldSpec {
public final TypeName type;
public final String name;
public final CodeBlock javadoc;
public final List<AnnotationSpec> annotations;
public final Set<Modifier> modifiers;
public final CodeBlock initializer;
private FieldSpec(Builder builder) {
this.type = checkNotNull(builder.type, "type == null");
this.name = checkNotNull(builder.name, "name == null");
this.javadoc = builder.javadoc.build();
this.annotations = Util.immutableList(builder.annotations);
this.modifiers = Util.immutableSet(builder.modifiers);
this.initializer = (builder.initializer == null)
? CodeBlock.builder().build()
: builder.initializer;
}
public boolean hasModifier(Modifier modifier) {
return modifiers.contains(modifier);
}
void emit(CodeWriter codeWriter, Set<Modifier> implicitModifiers) throws IOException {
codeWriter.emitJavadoc(javadoc);
codeWriter.emitAnnotations(annotations, false);
codeWriter.emitModifiers(modifiers, implicitModifiers);
codeWriter.emit("$T $L", type, name);
if (!initializer.isEmpty()) {
codeWriter.emit(" = ");
codeWriter.emit(initializer);
}
codeWriter.emit(";\n");
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
CodeWriter codeWriter = new CodeWriter(out);
emit(codeWriter, Collections.emptySet());
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public static Builder builder(TypeName type, String name, Modifier... modifiers) {
checkNotNull(type, "type == null");
checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
return new Builder(type, name)
.addModifiers(modifiers);
}
public static Builder builder(Type type, String name, Modifier... modifiers) {
return builder(TypeName.get(type), name, modifiers);
}
public Builder toBuilder() {
Builder builder = new Builder(type, name);
builder.javadoc.add(javadoc);
builder.annotations.addAll(annotations);
builder.modifiers.addAll(modifiers);
builder.initializer = initializer.isEmpty() ? null : initializer;
return builder;
}
public static final class Builder {
private final TypeName type;
private final String name;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
private CodeBlock initializer = null;
public final List<AnnotationSpec> annotations = new ArrayList<>();
public final List<Modifier> modifiers = new ArrayList<>();
private Builder(TypeName type, String name) {
this.type = type;
this.name = name;
}
public Builder addJavadoc(String format, Object... args) {
javadoc.add(format, args);
return this;
}
public Builder addJavadoc(CodeBlock block) {
javadoc.add(block);
return this;
}
public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
checkArgument(annotationSpecs != null, "annotationSpecs == null");
for (AnnotationSpec annotationSpec : annotationSpecs) {
this.annotations.add(annotationSpec);
}
return this;
}
public Builder addAnnotation(AnnotationSpec annotationSpec) {
this.annotations.add(annotationSpec);
return this;
}
public Builder addAnnotation(ClassName annotation) {
this.annotations.add(AnnotationSpec.builder(annotation).build());
return this;
}
public Builder addAnnotation(Class<?> annotation) {
return addAnnotation(ClassName.get(annotation));
}
public Builder addModifiers(Modifier... modifiers) {
Collections.addAll(this.modifiers, modifiers);
return this;
}
public Builder initializer(String format, Object... args) {
return initializer(CodeBlock.of(format, args));
}
public Builder initializer(CodeBlock codeBlock) {
checkState(this.initializer == null, "initializer was already set");
this.initializer = checkNotNull(codeBlock, "codeBlock == null");
return this;
}
public FieldSpec build() {
return new FieldSpec(this);
}
}
}

View file

@ -0,0 +1,342 @@
package org.xbib.javapoet;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
/**
* A Java file containing a single top level class.
*/
public final class JavaFile {
private static final Appendable NULL_APPENDABLE = new Appendable() {
@Override
public Appendable append(CharSequence charSequence) {
return this;
}
@Override
public Appendable append(CharSequence charSequence, int start, int end) {
return this;
}
@Override
public Appendable append(char c) {
return this;
}
};
public final CodeBlock fileComment;
public final String packageName;
public final TypeSpec typeSpec;
public final boolean skipJavaLangImports;
private final Set<String> staticImports;
private final Set<String> alwaysQualify;
private final String indent;
private JavaFile(Builder builder) {
this.fileComment = builder.fileComment.build();
this.packageName = builder.packageName;
this.typeSpec = builder.typeSpec;
this.skipJavaLangImports = builder.skipJavaLangImports;
this.staticImports = Util.immutableSet(builder.staticImports);
this.indent = builder.indent;
Set<String> alwaysQualifiedNames = new LinkedHashSet<>();
fillAlwaysQualifiedNames(builder.typeSpec, alwaysQualifiedNames);
this.alwaysQualify = Util.immutableSet(alwaysQualifiedNames);
}
private void fillAlwaysQualifiedNames(TypeSpec spec, Set<String> alwaysQualifiedNames) {
alwaysQualifiedNames.addAll(spec.alwaysQualifiedNames);
for (TypeSpec nested : spec.typeSpecs) {
fillAlwaysQualifiedNames(nested, alwaysQualifiedNames);
}
}
public void writeTo(Appendable out) throws IOException {
// First pass: emit the entire class, just to collect the types we'll need to import.
CodeWriter importsCollector = new CodeWriter(
NULL_APPENDABLE,
indent,
staticImports,
alwaysQualify
);
emit(importsCollector);
Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();
// Second pass: write the code, taking advantage of the imports.
CodeWriter codeWriter
= new CodeWriter(out, indent, suggestedImports, staticImports, alwaysQualify);
emit(codeWriter);
}
/**
* Writes this to {@code directory} as UTF-8 using the standard directory structure.
*/
public void writeTo(Path directory) throws IOException {
writeToPath(directory);
}
/**
* Writes this to {@code directory} with the provided {@code charset} using the standard directory
* structure.
*/
public void writeTo(Path directory, Charset charset) throws IOException {
writeToPath(directory, charset);
}
/**
* Writes this to {@code directory} as UTF-8 using the standard directory structure.
* Returns the {@link Path} instance to which source is actually written.
*/
public Path writeToPath(Path directory) throws IOException {
return writeToPath(directory, UTF_8);
}
/**
* Writes this to {@code directory} with the provided {@code charset} using the standard directory
* structure.
* Returns the {@link Path} instance to which source is actually written.
*/
public Path writeToPath(Path directory, Charset charset) throws IOException {
checkArgument(Files.notExists(directory) || Files.isDirectory(directory),
"path %s exists but is not a directory.", directory);
Path outputDirectory = directory;
if (!packageName.isEmpty()) {
for (String packageComponent : packageName.split("\\.")) {
outputDirectory = outputDirectory.resolve(packageComponent);
}
Files.createDirectories(outputDirectory);
}
Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), charset)) {
writeTo(writer);
}
return outputPath;
}
/**
* Writes this to {@code directory} as UTF-8 using the standard directory structure.
*/
public void writeTo(File directory) throws IOException {
writeTo(directory.toPath());
}
/**
* Writes this to {@code directory} as UTF-8 using the standard directory structure.
* Returns the {@link File} instance to which source is actually written.
*/
public File writeToFile(File directory) throws IOException {
final Path outputPath = writeToPath(directory.toPath());
return outputPath.toFile();
}
/**
* Writes this to {@code filer}.
*/
public void writeTo(Filer filer) throws IOException {
String fileName = packageName.isEmpty()
? typeSpec.name
: packageName + "." + typeSpec.name;
List<Element> originatingElements = typeSpec.originatingElements;
JavaFileObject filerSourceFile = filer.createSourceFile(fileName,
originatingElements.toArray(new Element[originatingElements.size()]));
try (Writer writer = filerSourceFile.openWriter()) {
writeTo(writer);
} catch (Exception e) {
try {
filerSourceFile.delete();
} catch (Exception ignored) {
}
throw e;
}
}
private void emit(CodeWriter codeWriter) throws IOException {
codeWriter.pushPackage(packageName);
if (!fileComment.isEmpty()) {
codeWriter.emitComment(fileComment);
}
if (!packageName.isEmpty()) {
codeWriter.emit("package $L;\n", packageName);
codeWriter.emit("\n");
}
if (!staticImports.isEmpty()) {
for (String signature : staticImports) {
codeWriter.emit("import static $L;\n", signature);
}
codeWriter.emit("\n");
}
int importedTypesCount = 0;
for (ClassName className : new TreeSet<>(codeWriter.importedTypes().values())) {
// TODO what about nested types like java.util.Map.Entry?
if (skipJavaLangImports
&& className.packageName().equals("java.lang")
&& !alwaysQualify.contains(className.simpleName)) {
continue;
}
codeWriter.emit("import $L;\n", className.withoutAnnotations());
importedTypesCount++;
}
if (importedTypesCount > 0) {
codeWriter.emit("\n");
}
typeSpec.emit(codeWriter, null, Collections.emptySet());
codeWriter.popPackage();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
try {
StringBuilder result = new StringBuilder();
writeTo(result);
return result.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public JavaFileObject toJavaFileObject() {
URI uri = URI.create((packageName.isEmpty()
? typeSpec.name
: packageName.replace('.', '/') + '/' + typeSpec.name)
+ Kind.SOURCE.extension);
return new SimpleJavaFileObject(uri, Kind.SOURCE) {
private final long lastModified = System.currentTimeMillis();
@Override
public String getCharContent(boolean ignoreEncodingErrors) {
return JavaFile.this.toString();
}
@Override
public InputStream openInputStream() throws IOException {
return new ByteArrayInputStream(getCharContent(true).getBytes(UTF_8));
}
@Override
public long getLastModified() {
return lastModified;
}
};
}
public static Builder builder(String packageName, TypeSpec typeSpec) {
checkNotNull(packageName, "packageName == null");
checkNotNull(typeSpec, "typeSpec == null");
return new Builder(packageName, typeSpec);
}
public Builder toBuilder() {
Builder builder = new Builder(packageName, typeSpec);
builder.fileComment.add(fileComment);
builder.skipJavaLangImports = skipJavaLangImports;
builder.indent = indent;
return builder;
}
public static final class Builder {
private final String packageName;
private final TypeSpec typeSpec;
private final CodeBlock.Builder fileComment = CodeBlock.builder();
private boolean skipJavaLangImports;
private String indent = " ";
public final Set<String> staticImports = new TreeSet<>();
private Builder(String packageName, TypeSpec typeSpec) {
this.packageName = packageName;
this.typeSpec = typeSpec;
}
public Builder addFileComment(String format, Object... args) {
this.fileComment.add(format, args);
return this;
}
public Builder addStaticImport(Enum<?> constant) {
return addStaticImport(ClassName.get(constant.getDeclaringClass()), constant.name());
}
public Builder addStaticImport(Class<?> clazz, String... names) {
return addStaticImport(ClassName.get(clazz), names);
}
public Builder addStaticImport(ClassName className, String... names) {
checkArgument(className != null, "className == null");
checkArgument(names != null, "names == null");
checkArgument(names.length > 0, "names array is empty");
for (String name : names) {
checkArgument(name != null, "null entry in names array: %s", Arrays.toString(names));
staticImports.add(className.canonicalName + "." + name);
}
return this;
}
/**
* Call this to omit imports for classes in {@code java.lang}, such as {@code java.lang.String}.
*
* <p>By default, JavaPoet explicitly imports types in {@code java.lang} to defend against
* naming conflicts. Suppose an (ill-advised) class is named {@code com.example.String}. When
* {@code java.lang} imports are skipped, generated code in {@code com.example} that references
* {@code java.lang.String} will get {@code com.example.String} instead.
*/
public Builder skipJavaLangImports(boolean skipJavaLangImports) {
this.skipJavaLangImports = skipJavaLangImports;
return this;
}
public Builder indent(String indent) {
this.indent = indent;
return this;
}
public JavaFile build() {
return new JavaFile(this);
}
}
}

View file

@ -0,0 +1,176 @@
package org.xbib.javapoet;
import java.io.IOException;
import static org.xbib.javapoet.Util.checkNotNull;
/**
* Implements soft line wrapping on an appendable. To use, append characters using {@link #append}
* or soft-wrapping spaces using {@link #wrappingSpace}.
*/
final class LineWrapper {
private final RecordingAppendable out;
private final String indent;
private final int columnLimit;
private boolean closed;
/**
* Characters written since the last wrapping space that haven't yet been flushed.
*/
private final StringBuilder buffer = new StringBuilder();
/**
* The number of characters since the most recent newline. Includes both out and the buffer.
*/
private int column = 0;
/**
* -1 if we have no buffering; otherwise the number of {@code indent}s to write after wrapping.
*/
private int indentLevel = -1;
/**
* Null if we have no buffering; otherwise the type to pass to the next call to {@link #flush}.
*/
private FlushType nextFlush;
LineWrapper(Appendable out, String indent, int columnLimit) {
checkNotNull(out, "out == null");
this.out = new RecordingAppendable(out);
this.indent = indent;
this.columnLimit = columnLimit;
}
/**
* @return the last emitted char or {@link Character#MIN_VALUE} if nothing emitted yet.
*/
char lastChar() {
return out.lastChar;
}
/**
* Emit {@code s}. This may be buffered to permit line wraps to be inserted.
*/
void append(String s) throws IOException {
if (closed) throw new IllegalStateException("closed");
if (nextFlush != null) {
int nextNewline = s.indexOf('\n');
// If s doesn't cause the current line to cross the limit, buffer it and return. We'll decide
// whether or not we have to wrap it later.
if (nextNewline == -1 && column + s.length() <= columnLimit) {
buffer.append(s);
column += s.length();
return;
}
// Wrap if appending s would overflow the current line.
boolean wrap = nextNewline == -1 || column + nextNewline > columnLimit;
flush(wrap ? FlushType.WRAP : nextFlush);
}
out.append(s);
int lastNewline = s.lastIndexOf('\n');
column = lastNewline != -1
? s.length() - lastNewline - 1
: column + s.length();
}
/**
* Emit either a space or a newline character.
*/
void wrappingSpace(int indentLevel) throws IOException {
if (closed) throw new IllegalStateException("closed");
if (this.nextFlush != null) flush(nextFlush);
column++; // Increment the column even though the space is deferred to next call to flush().
this.nextFlush = FlushType.SPACE;
this.indentLevel = indentLevel;
}
/**
* Emit a newline character if the line will exceed it's limit, otherwise do nothing.
*/
void zeroWidthSpace(int indentLevel) throws IOException {
if (closed) throw new IllegalStateException("closed");
if (column == 0) return;
if (this.nextFlush != null) flush(nextFlush);
this.nextFlush = FlushType.EMPTY;
this.indentLevel = indentLevel;
}
/**
* Flush any outstanding text and forbid future writes to this line wrapper.
*/
void close() throws IOException {
if (nextFlush != null) flush(nextFlush);
closed = true;
}
/**
* Write the space followed by any buffered text that follows it.
*/
private void flush(FlushType flushType) throws IOException {
switch (flushType) {
case WRAP:
out.append('\n');
for (int i = 0; i < indentLevel; i++) {
out.append(indent);
}
column = indentLevel * indent.length();
column += buffer.length();
break;
case SPACE:
out.append(' ');
break;
case EMPTY:
break;
default:
throw new IllegalArgumentException("Unknown FlushType: " + flushType);
}
out.append(buffer);
buffer.delete(0, buffer.length());
indentLevel = -1;
nextFlush = null;
}
private enum FlushType {
WRAP, SPACE, EMPTY;
}
/**
* A delegating {@link Appendable} that records info about the chars passing through it.
*/
static final class RecordingAppendable implements Appendable {
private final Appendable delegate;
char lastChar = Character.MIN_VALUE;
RecordingAppendable(Appendable delegate) {
this.delegate = delegate;
}
@Override
public Appendable append(CharSequence csq) throws IOException {
int length = csq.length();
if (length != 0) {
lastChar = csq.charAt(length - 1);
}
return delegate.append(csq);
}
@Override
public Appendable append(CharSequence csq, int start, int end) throws IOException {
CharSequence sub = csq.subSequence(start, end);
return append(sub);
}
@Override
public Appendable append(char c) throws IOException {
lastChar = c;
return delegate.append(c);
}
}
}

View file

@ -0,0 +1,527 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.util.Types;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
import static org.xbib.javapoet.Util.checkState;
/**
* A generated constructor or method declaration.
*/
public final class MethodSpec {
static final String CONSTRUCTOR = "<init>";
public final String name;
public final CodeBlock javadoc;
public final List<AnnotationSpec> annotations;
public final Set<Modifier> modifiers;
public final List<TypeVariableName> typeVariables;
public final TypeName returnType;
public final List<ParameterSpec> parameters;
public final boolean varargs;
public final List<TypeName> exceptions;
public final CodeBlock code;
public final CodeBlock defaultValue;
private MethodSpec(Builder builder) {
CodeBlock code = builder.code.build();
checkArgument(code.isEmpty() || !builder.modifiers.contains(Modifier.ABSTRACT),
"abstract method %s cannot have code", builder.name);
checkArgument(!builder.varargs || lastParameterIsArray(builder.parameters),
"last parameter of varargs method %s must be an array", builder.name);
this.name = checkNotNull(builder.name, "name == null");
this.javadoc = builder.javadoc.build();
this.annotations = Util.immutableList(builder.annotations);
this.modifiers = Util.immutableSet(builder.modifiers);
this.typeVariables = Util.immutableList(builder.typeVariables);
this.returnType = builder.returnType;
this.parameters = Util.immutableList(builder.parameters);
this.varargs = builder.varargs;
this.exceptions = Util.immutableList(builder.exceptions);
this.defaultValue = builder.defaultValue;
this.code = code;
}
private boolean lastParameterIsArray(List<ParameterSpec> parameters) {
return !parameters.isEmpty()
&& TypeName.asArray((parameters.get(parameters.size() - 1).type)) != null;
}
void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
throws IOException {
codeWriter.emitJavadoc(javadocWithParameters());
codeWriter.emitAnnotations(annotations, false);
codeWriter.emitModifiers(modifiers, implicitModifiers);
if (!typeVariables.isEmpty()) {
codeWriter.emitTypeVariables(typeVariables);
codeWriter.emit(" ");
}
if (isConstructor()) {
codeWriter.emit("$L($Z", enclosingName);
} else {
codeWriter.emit("$T $L($Z", returnType, name);
}
boolean firstParameter = true;
for (Iterator<ParameterSpec> i = parameters.iterator(); i.hasNext(); ) {
ParameterSpec parameter = i.next();
if (!firstParameter) codeWriter.emit(",").emitWrappingSpace();
parameter.emit(codeWriter, !i.hasNext() && varargs);
firstParameter = false;
}
codeWriter.emit(")");
if (defaultValue != null && !defaultValue.isEmpty()) {
codeWriter.emit(" default ");
codeWriter.emit(defaultValue);
}
if (!exceptions.isEmpty()) {
codeWriter.emitWrappingSpace().emit("throws");
boolean firstException = true;
for (TypeName exception : exceptions) {
if (!firstException) codeWriter.emit(",");
codeWriter.emitWrappingSpace().emit("$T", exception);
firstException = false;
}
}
if (hasModifier(Modifier.ABSTRACT)) {
codeWriter.emit(";\n");
} else if (hasModifier(Modifier.NATIVE)) {
// Code is allowed to support stuff like GWT JSNI.
codeWriter.emit(code);
codeWriter.emit(";\n");
} else {
codeWriter.emit(" {\n");
codeWriter.indent();
codeWriter.emit(code, true);
codeWriter.unindent();
codeWriter.emit("}\n");
}
codeWriter.popTypeVariables(typeVariables);
}
private CodeBlock javadocWithParameters() {
CodeBlock.Builder builder = javadoc.toBuilder();
boolean emitTagNewline = true;
for (ParameterSpec parameterSpec : parameters) {
if (!parameterSpec.javadoc.isEmpty()) {
// Emit a new line before @param section only if the method javadoc is present.
if (emitTagNewline && !javadoc.isEmpty()) builder.add("\n");
emitTagNewline = false;
builder.add("@param $L $L", parameterSpec.name, parameterSpec.javadoc);
}
}
return builder.build();
}
public boolean hasModifier(Modifier modifier) {
return modifiers.contains(modifier);
}
public boolean isConstructor() {
return name.equals(CONSTRUCTOR);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
CodeWriter codeWriter = new CodeWriter(out);
emit(codeWriter, "Constructor", Collections.emptySet());
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public static Builder methodBuilder(String name) {
return new Builder(name);
}
public static Builder constructorBuilder() {
return new Builder(CONSTRUCTOR);
}
/**
* Returns a new method spec builder that overrides {@code method}.
*
* <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and
* throws declarations. An {@link Override} annotation will be added.
*
* <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and
* parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately.
*/
public static Builder overriding(ExecutableElement method) {
checkNotNull(method, "method == null");
Element enclosingClass = method.getEnclosingElement();
if (enclosingClass.getModifiers().contains(Modifier.FINAL)) {
throw new IllegalArgumentException("Cannot override method on final class " + enclosingClass);
}
Set<Modifier> modifiers = method.getModifiers();
if (modifiers.contains(Modifier.PRIVATE)
|| modifiers.contains(Modifier.FINAL)
|| modifiers.contains(Modifier.STATIC)) {
throw new IllegalArgumentException("cannot override method with modifiers: " + modifiers);
}
String methodName = method.getSimpleName().toString();
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder(methodName);
methodBuilder.addAnnotation(Override.class);
modifiers = new LinkedHashSet<>(modifiers);
modifiers.remove(Modifier.ABSTRACT);
modifiers.remove(Modifier.DEFAULT);
methodBuilder.addModifiers(modifiers);
for (TypeParameterElement typeParameterElement : method.getTypeParameters()) {
TypeVariable var = (TypeVariable) typeParameterElement.asType();
methodBuilder.addTypeVariable(TypeVariableName.get(var));
}
methodBuilder.returns(TypeName.get(method.getReturnType()));
methodBuilder.addParameters(ParameterSpec.parametersOf(method));
methodBuilder.varargs(method.isVarArgs());
for (TypeMirror thrownType : method.getThrownTypes()) {
methodBuilder.addException(TypeName.get(thrownType));
}
return methodBuilder;
}
/**
* Returns a new method spec builder that overrides {@code method} as a member of {@code
* enclosing}. This will resolve type parameters: for example overriding {@link
* Comparable#compareTo} in a type that implements {@code Comparable<Movie>}, the {@code T}
* parameter will be resolved to {@code Movie}.
*
* <p>This will copy its visibility modifiers, type parameters, return type, name, parameters, and
* throws declarations. An {@link Override} annotation will be added.
*
* <p>Note that in JavaPoet 1.2 through 1.7 this method retained annotations from the method and
* parameters of the overridden method. Since JavaPoet 1.8 annotations must be added separately.
*/
public static Builder overriding(
ExecutableElement method, DeclaredType enclosing, Types types) {
ExecutableType executableType = (ExecutableType) types.asMemberOf(enclosing, method);
List<? extends TypeMirror> resolvedParameterTypes = executableType.getParameterTypes();
List<? extends TypeMirror> resolvedThrownTypes = executableType.getThrownTypes();
TypeMirror resolvedReturnType = executableType.getReturnType();
Builder builder = overriding(method);
builder.returns(TypeName.get(resolvedReturnType));
for (int i = 0, size = builder.parameters.size(); i < size; i++) {
ParameterSpec parameter = builder.parameters.get(i);
TypeName type = TypeName.get(resolvedParameterTypes.get(i));
builder.parameters.set(i, parameter.toBuilder(type, parameter.name).build());
}
builder.exceptions.clear();
for (int i = 0, size = resolvedThrownTypes.size(); i < size; i++) {
builder.addException(TypeName.get(resolvedThrownTypes.get(i)));
}
return builder;
}
public Builder toBuilder() {
Builder builder = new Builder(name);
builder.javadoc.add(javadoc);
builder.annotations.addAll(annotations);
builder.modifiers.addAll(modifiers);
builder.typeVariables.addAll(typeVariables);
builder.returnType = returnType;
builder.parameters.addAll(parameters);
builder.exceptions.addAll(exceptions);
builder.code.add(code);
builder.varargs = varargs;
builder.defaultValue = defaultValue;
return builder;
}
public static final class Builder {
private String name;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
private TypeName returnType;
private final Set<TypeName> exceptions = new LinkedHashSet<>();
private final CodeBlock.Builder code = CodeBlock.builder();
private boolean varargs;
private CodeBlock defaultValue;
public final List<TypeVariableName> typeVariables = new ArrayList<>();
public final List<AnnotationSpec> annotations = new ArrayList<>();
public final List<Modifier> modifiers = new ArrayList<>();
public final List<ParameterSpec> parameters = new ArrayList<>();
private Builder(String name) {
setName(name);
}
public Builder setName(String name) {
checkNotNull(name, "name == null");
checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name),
"not a valid name: %s", name);
this.name = name;
this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID;
return this;
}
public Builder addJavadoc(String format, Object... args) {
javadoc.add(format, args);
return this;
}
public Builder addJavadoc(CodeBlock block) {
javadoc.add(block);
return this;
}
public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
checkArgument(annotationSpecs != null, "annotationSpecs == null");
for (AnnotationSpec annotationSpec : annotationSpecs) {
this.annotations.add(annotationSpec);
}
return this;
}
public Builder addAnnotation(AnnotationSpec annotationSpec) {
this.annotations.add(annotationSpec);
return this;
}
public Builder addAnnotation(ClassName annotation) {
this.annotations.add(AnnotationSpec.builder(annotation).build());
return this;
}
public Builder addAnnotation(Class<?> annotation) {
return addAnnotation(ClassName.get(annotation));
}
public Builder addModifiers(Modifier... modifiers) {
checkNotNull(modifiers, "modifiers == null");
Collections.addAll(this.modifiers, modifiers);
return this;
}
public Builder addModifiers(Iterable<Modifier> modifiers) {
checkNotNull(modifiers, "modifiers == null");
for (Modifier modifier : modifiers) {
this.modifiers.add(modifier);
}
return this;
}
public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
checkArgument(typeVariables != null, "typeVariables == null");
for (TypeVariableName typeVariable : typeVariables) {
this.typeVariables.add(typeVariable);
}
return this;
}
public Builder addTypeVariable(TypeVariableName typeVariable) {
typeVariables.add(typeVariable);
return this;
}
public Builder returns(TypeName returnType) {
checkState(!name.equals(CONSTRUCTOR), "constructor cannot have return type.");
this.returnType = returnType;
return this;
}
public Builder returns(Type returnType) {
return returns(TypeName.get(returnType));
}
public Builder addParameters(Iterable<ParameterSpec> parameterSpecs) {
checkArgument(parameterSpecs != null, "parameterSpecs == null");
for (ParameterSpec parameterSpec : parameterSpecs) {
this.parameters.add(parameterSpec);
}
return this;
}
public Builder addParameter(ParameterSpec parameterSpec) {
this.parameters.add(parameterSpec);
return this;
}
public Builder addParameter(TypeName type, String name, Modifier... modifiers) {
return addParameter(ParameterSpec.builder(type, name, modifiers).build());
}
public Builder addParameter(Type type, String name, Modifier... modifiers) {
return addParameter(TypeName.get(type), name, modifiers);
}
public Builder varargs() {
return varargs(true);
}
public Builder varargs(boolean varargs) {
this.varargs = varargs;
return this;
}
public Builder addExceptions(Iterable<? extends TypeName> exceptions) {
checkArgument(exceptions != null, "exceptions == null");
for (TypeName exception : exceptions) {
this.exceptions.add(exception);
}
return this;
}
public Builder addException(TypeName exception) {
this.exceptions.add(exception);
return this;
}
public Builder addException(Type exception) {
return addException(TypeName.get(exception));
}
public Builder addCode(String format, Object... args) {
code.add(format, args);
return this;
}
public Builder addNamedCode(String format, Map<String, ?> args) {
code.addNamed(format, args);
return this;
}
public Builder addCode(CodeBlock codeBlock) {
code.add(codeBlock);
return this;
}
public Builder addComment(String format, Object... args) {
code.add("// " + format + "\n", args);
return this;
}
public Builder defaultValue(String format, Object... args) {
return defaultValue(CodeBlock.of(format, args));
}
public Builder defaultValue(CodeBlock codeBlock) {
checkState(this.defaultValue == null, "defaultValue was already set");
this.defaultValue = checkNotNull(codeBlock, "codeBlock == null");
return this;
}
/**
* @param controlFlow the control flow construct and its code, such as "if (foo == 5)".
* Shouldn't contain braces or newline characters.
*/
public Builder beginControlFlow(String controlFlow, Object... args) {
code.beginControlFlow(controlFlow, args);
return this;
}
/**
* @param codeBlock the control flow construct and its code, such as "if (foo == 5)".
* Shouldn't contain braces or newline characters.
*/
public Builder beginControlFlow(CodeBlock codeBlock) {
return beginControlFlow("$L", codeBlock);
}
/**
* @param controlFlow the control flow construct and its code, such as "else if (foo == 10)".
* Shouldn't contain braces or newline characters.
*/
public Builder nextControlFlow(String controlFlow, Object... args) {
code.nextControlFlow(controlFlow, args);
return this;
}
/**
* @param codeBlock the control flow construct and its code, such as "else if (foo == 10)".
* Shouldn't contain braces or newline characters.
*/
public Builder nextControlFlow(CodeBlock codeBlock) {
return nextControlFlow("$L", codeBlock);
}
public Builder endControlFlow() {
code.endControlFlow();
return this;
}
/**
* @param controlFlow the optional control flow construct and its code, such as
* "while(foo == 20)". Only used for "do/while" control flows.
*/
public Builder endControlFlow(String controlFlow, Object... args) {
code.endControlFlow(controlFlow, args);
return this;
}
/**
* @param codeBlock the optional control flow construct and its code, such as
* "while(foo == 20)". Only used for "do/while" control flows.
*/
public Builder endControlFlow(CodeBlock codeBlock) {
return endControlFlow("$L", codeBlock);
}
public Builder addStatement(String format, Object... args) {
code.addStatement(format, args);
return this;
}
public Builder addStatement(CodeBlock codeBlock) {
code.addStatement(codeBlock);
return this;
}
public MethodSpec build() {
return new MethodSpec(this);
}
}
}

View file

@ -0,0 +1,154 @@
package org.xbib.javapoet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.lang.model.SourceVersion;
import static org.xbib.javapoet.Util.checkNotNull;
/**
* Assigns Java identifier names to avoid collisions, keywords, and invalid characters. To use,
* first create an instance and allocate all of the names that you need. Typically this is a
* mix of user-supplied names and constants: <pre> {@code
*
* NameAllocator nameAllocator = new NameAllocator();
* for (MyProperty property : properties) {
* nameAllocator.newName(property.name(), property);
* }
* nameAllocator.newName("sb", "string builder");
* }</pre>
* <p>
* Pass a unique tag object to each allocation. The tag scopes the name, and can be used to look up
* the allocated name later. Typically the tag is the object that is being named. In the above
* example we use {@code property} for the user-supplied property names, and {@code "string
* builder"} for our constant string builder.
*
* <p>Once we've allocated names we can use them when generating code: <pre> {@code
*
* MethodSpec.Builder builder = MethodSpec.methodBuilder("toString")
* .addAnnotation(Override.class)
* .addModifiers(Modifier.PUBLIC)
* .returns(String.class);
*
* builder.addStatement("$1T $2N = new $1T()",
* StringBuilder.class, nameAllocator.get("string builder"));
* for (MyProperty property : properties) {
* builder.addStatement("$N.append($N)",
* nameAllocator.get("string builder"), nameAllocator.get(property));
* }
* builder.addStatement("return $N", nameAllocator.get("string builder"));
* return builder.build();
* }</pre>
* <p>
* The above code generates unique names if presented with conflicts. Given user-supplied properties
* with names {@code ab} and {@code sb} this generates the following: <pre> {@code
*
* &#64;Override
* public String toString() {
* StringBuilder sb_ = new StringBuilder();
* sb_.append(ab);
* sb_.append(sb);
* return sb_.toString();
* }
* }</pre>
* <p>
* The underscore is appended to {@code sb} to avoid conflicting with the user-supplied {@code sb}
* property. Underscores are also prefixed for names that start with a digit, and used to replace
* name-unsafe characters like space or dash.
*
* <p>When dealing with multiple independent inner scopes, use a {@link #clone()} of the
* NameAllocator used for the outer scope to further refine name allocation for a specific inner
* scope.
*/
public final class NameAllocator implements Cloneable {
private final Set<String> allocatedNames;
private final Map<Object, String> tagToName;
public NameAllocator() {
this(new LinkedHashSet<>(), new LinkedHashMap<>());
}
private NameAllocator(LinkedHashSet<String> allocatedNames,
LinkedHashMap<Object, String> tagToName) {
this.allocatedNames = allocatedNames;
this.tagToName = tagToName;
}
/**
* Return a new name using {@code suggestion} that will not be a Java identifier or clash with
* other names.
*/
public String newName(String suggestion) {
return newName(suggestion, UUID.randomUUID().toString());
}
/**
* Return a new name using {@code suggestion} that will not be a Java identifier or clash with
* other names. The returned value can be queried multiple times by passing {@code tag} to
* {@link #get(Object)}.
*/
public String newName(String suggestion, Object tag) {
checkNotNull(suggestion, "suggestion");
checkNotNull(tag, "tag");
suggestion = toJavaIdentifier(suggestion);
while (SourceVersion.isKeyword(suggestion) || !allocatedNames.add(suggestion)) {
suggestion = suggestion + "_";
}
String replaced = tagToName.put(tag, suggestion);
if (replaced != null) {
tagToName.put(tag, replaced); // Put things back as they were!
throw new IllegalArgumentException("tag " + tag + " cannot be used for both '" + replaced
+ "' and '" + suggestion + "'");
}
return suggestion;
}
public static String toJavaIdentifier(String suggestion) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < suggestion.length(); ) {
int codePoint = suggestion.codePointAt(i);
if (i == 0
&& !Character.isJavaIdentifierStart(codePoint)
&& Character.isJavaIdentifierPart(codePoint)) {
result.append("_");
}
int validCodePoint = Character.isJavaIdentifierPart(codePoint) ? codePoint : '_';
result.appendCodePoint(validCodePoint);
i += Character.charCount(codePoint);
}
return result.toString();
}
/**
* Retrieve a name created with {@link #newName(String, Object)}.
*/
public String get(Object tag) {
String result = tagToName.get(tag);
if (result == null) {
throw new IllegalArgumentException("unknown tag: " + tag);
}
return result;
}
/**
* Create a deep copy of this NameAllocator. Useful to create multiple independent refinements
* of a NameAllocator to be used in the respective definition of multiples, independently-scoped,
* inner code blocks.
*
* @return A deep copy of this NameAllocator.
*/
@Override
public NameAllocator clone() {
return new NameAllocator(
new LinkedHashSet<>(this.allocatedNames),
new LinkedHashMap<>(this.tagToName));
}
}

View file

@ -0,0 +1,191 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.VariableElement;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
/**
* A generated parameter declaration.
*/
public final class ParameterSpec {
public final String name;
public final List<AnnotationSpec> annotations;
public final Set<Modifier> modifiers;
public final TypeName type;
public final CodeBlock javadoc;
private ParameterSpec(Builder builder) {
this.name = checkNotNull(builder.name, "name == null");
this.annotations = Util.immutableList(builder.annotations);
this.modifiers = Util.immutableSet(builder.modifiers);
this.type = checkNotNull(builder.type, "type == null");
this.javadoc = builder.javadoc.build();
}
public boolean hasModifier(Modifier modifier) {
return modifiers.contains(modifier);
}
void emit(CodeWriter codeWriter, boolean varargs) throws IOException {
codeWriter.emitAnnotations(annotations, true);
codeWriter.emitModifiers(modifiers);
if (varargs) {
TypeName.asArray(type).emit(codeWriter, true);
} else {
type.emit(codeWriter);
}
codeWriter.emit(" $L", name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
CodeWriter codeWriter = new CodeWriter(out);
emit(codeWriter, false);
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public static ParameterSpec get(VariableElement element) {
checkArgument(element.getKind().equals(ElementKind.PARAMETER), "element is not a parameter");
TypeName type = TypeName.get(element.asType());
String name = element.getSimpleName().toString();
// Copying parameter annotations can be incorrect so we're deliberately not including them.
// See https://github.com/square/javapoet/issues/482.
return ParameterSpec.builder(type, name)
.addModifiers(element.getModifiers())
.build();
}
static List<ParameterSpec> parametersOf(ExecutableElement method) {
List<ParameterSpec> result = new ArrayList<>();
for (VariableElement parameter : method.getParameters()) {
result.add(ParameterSpec.get(parameter));
}
return result;
}
private static boolean isValidParameterName(String name) {
// Allow "this" for explicit receiver parameters
// See https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.1.
if (name.endsWith(".this")) {
return SourceVersion.isIdentifier(name.substring(0, name.length() - ".this".length()));
}
return name.equals("this") || SourceVersion.isName(name);
}
public static Builder builder(TypeName type, String name, Modifier... modifiers) {
checkNotNull(type, "type == null");
checkArgument(isValidParameterName(name), "not a valid name: %s", name);
return new Builder(type, name)
.addModifiers(modifiers);
}
public static Builder builder(Type type, String name, Modifier... modifiers) {
return builder(TypeName.get(type), name, modifiers);
}
public Builder toBuilder() {
return toBuilder(type, name);
}
Builder toBuilder(TypeName type, String name) {
Builder builder = new Builder(type, name);
builder.annotations.addAll(annotations);
builder.modifiers.addAll(modifiers);
return builder;
}
public static final class Builder {
private final TypeName type;
private final String name;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
public final List<AnnotationSpec> annotations = new ArrayList<>();
public final List<Modifier> modifiers = new ArrayList<>();
private Builder(TypeName type, String name) {
this.type = type;
this.name = name;
}
public Builder addJavadoc(String format, Object... args) {
javadoc.add(format, args);
return this;
}
public Builder addJavadoc(CodeBlock block) {
javadoc.add(block);
return this;
}
public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
checkArgument(annotationSpecs != null, "annotationSpecs == null");
for (AnnotationSpec annotationSpec : annotationSpecs) {
this.annotations.add(annotationSpec);
}
return this;
}
public Builder addAnnotation(AnnotationSpec annotationSpec) {
this.annotations.add(annotationSpec);
return this;
}
public Builder addAnnotation(ClassName annotation) {
this.annotations.add(AnnotationSpec.builder(annotation).build());
return this;
}
public Builder addAnnotation(Class<?> annotation) {
return addAnnotation(ClassName.get(annotation));
}
public Builder addModifiers(Modifier... modifiers) {
Collections.addAll(this.modifiers, modifiers);
return this;
}
public Builder addModifiers(Iterable<Modifier> modifiers) {
checkNotNull(modifiers, "modifiers == null");
for (Modifier modifier : modifiers) {
if (!modifier.equals(Modifier.FINAL)) {
throw new IllegalStateException("unexpected parameter modifier: " + modifier);
}
this.modifiers.add(modifier);
}
return this;
}
public ParameterSpec build() {
return new ParameterSpec(this);
}
}
}

View file

@ -0,0 +1,132 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
public final class ParameterizedTypeName extends TypeName {
private final ParameterizedTypeName enclosingType;
public final ClassName rawType;
public final List<TypeName> typeArguments;
ParameterizedTypeName(ParameterizedTypeName enclosingType, ClassName rawType,
List<TypeName> typeArguments) {
this(enclosingType, rawType, typeArguments, new ArrayList<>());
}
private ParameterizedTypeName(ParameterizedTypeName enclosingType, ClassName rawType,
List<TypeName> typeArguments, List<AnnotationSpec> annotations) {
super(annotations);
this.rawType = checkNotNull(rawType, "rawType == null").annotated(annotations);
this.enclosingType = enclosingType;
this.typeArguments = Util.immutableList(typeArguments);
checkArgument(!this.typeArguments.isEmpty() || enclosingType != null,
"no type arguments: %s", rawType);
for (TypeName typeArgument : this.typeArguments) {
checkArgument(!typeArgument.isPrimitive() && typeArgument != VOID,
"invalid type parameter: %s", typeArgument);
}
}
@Override
public ParameterizedTypeName annotated(List<AnnotationSpec> annotations) {
return new ParameterizedTypeName(
enclosingType, rawType, typeArguments, concatAnnotations(annotations));
}
@Override
public TypeName withoutAnnotations() {
return new ParameterizedTypeName(
enclosingType, rawType.withoutAnnotations(), typeArguments, new ArrayList<>());
}
@Override
CodeWriter emit(CodeWriter out) throws IOException {
if (enclosingType != null) {
enclosingType.emit(out);
out.emit(".");
if (isAnnotated()) {
out.emit(" ");
emitAnnotations(out);
}
out.emit(rawType.simpleName());
} else {
rawType.emit(out);
}
if (!typeArguments.isEmpty()) {
out.emitAndIndent("<");
boolean firstParameter = true;
for (TypeName parameter : typeArguments) {
if (!firstParameter) out.emitAndIndent(", ");
parameter.emit(out);
firstParameter = false;
}
out.emitAndIndent(">");
}
return out;
}
/**
* Returns a new {@link ParameterizedTypeName} instance for the specified {@code name} as nested
* inside this class.
*/
public ParameterizedTypeName nestedClass(String name) {
checkNotNull(name, "name == null");
return new ParameterizedTypeName(this, rawType.nestedClass(name), new ArrayList<>(),
new ArrayList<>());
}
/**
* Returns a new {@link ParameterizedTypeName} instance for the specified {@code name} as nested
* inside this class, with the specified {@code typeArguments}.
*/
public ParameterizedTypeName nestedClass(String name, List<TypeName> typeArguments) {
checkNotNull(name, "name == null");
return new ParameterizedTypeName(this, rawType.nestedClass(name), typeArguments,
new ArrayList<>());
}
/**
* Returns a parameterized type, applying {@code typeArguments} to {@code rawType}.
*/
public static ParameterizedTypeName get(ClassName rawType, TypeName... typeArguments) {
return new ParameterizedTypeName(null, rawType, Arrays.asList(typeArguments));
}
/**
* Returns a parameterized type, applying {@code typeArguments} to {@code rawType}.
*/
public static ParameterizedTypeName get(Class<?> rawType, Type... typeArguments) {
return new ParameterizedTypeName(null, ClassName.get(rawType), list(typeArguments));
}
/**
* Returns a parameterized type equivalent to {@code type}.
*/
public static ParameterizedTypeName get(ParameterizedType type) {
return get(type, new LinkedHashMap<>());
}
/**
* Returns a parameterized type equivalent to {@code type}.
*/
static ParameterizedTypeName get(ParameterizedType type, Map<Type, TypeVariableName> map) {
ClassName rawType = ClassName.get((Class<?>) type.getRawType());
ParameterizedType ownerType = (type.getOwnerType() instanceof ParameterizedType)
&& !Modifier.isStatic(((Class<?>) type.getRawType()).getModifiers())
? (ParameterizedType) type.getOwnerType() : null;
List<TypeName> typeArguments = TypeName.list(type.getActualTypeArguments(), map);
return (ownerType != null)
? get(ownerType, map).nestedClass(rawType.simpleName(), typeArguments)
: new ParameterizedTypeName(null, rawType, typeArguments);
}
}

View file

@ -0,0 +1,404 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleTypeVisitor8;
/**
* Any type in Java's type system, plus {@code void}. This class is an identifier for primitive
* types like {@code int} and raw reference types like {@code String} and {@code List}. It also
* identifies composite types like {@code char[]} and {@code Set<Long>}.
*
* <p>Type names are dumb identifiers only and do not model the values they name. For example, the
* type name for {@code java.util.List} doesn't know about the {@code size()} method, the fact that
* lists are collections, or even that it accepts a single type parameter.
*
* <p>Instances of this class are immutable value objects that implement {@code equals()} and {@code
* hashCode()} properly.
*
* <h3>Referencing existing types</h3>
*
* <p>Primitives and void are constants that you can reference directly: see {@link #INT}, {@link
* #DOUBLE}, and {@link #VOID}.
*
* <p>In an annotation processor you can get a type name instance for a type mirror by calling
* {@link #get(TypeMirror)}. In reflection code, you can use {@link #get(Type)}.
*
* <h3>Defining new types</h3>
*
* <p>Create new reference types like {@code com.example.HelloWorld} with {@link
* ClassName#get(String, String, String...)}. To build composite types like {@code char[]} and
* {@code Set<Long>}, use the factory methods on {@link ArrayTypeName}, {@link
* ParameterizedTypeName}, {@link TypeVariableName}, and {@link WildcardTypeName}.
*/
public class TypeName {
public static final TypeName VOID = new TypeName("void");
public static final TypeName BOOLEAN = new TypeName("boolean");
public static final TypeName BYTE = new TypeName("byte");
public static final TypeName SHORT = new TypeName("short");
public static final TypeName INT = new TypeName("int");
public static final TypeName LONG = new TypeName("long");
public static final TypeName CHAR = new TypeName("char");
public static final TypeName FLOAT = new TypeName("float");
public static final TypeName DOUBLE = new TypeName("double");
public static final ClassName OBJECT = ClassName.get("java.lang", "Object");
private static final ClassName BOXED_VOID = ClassName.get("java.lang", "Void");
private static final ClassName BOXED_BOOLEAN = ClassName.get("java.lang", "Boolean");
private static final ClassName BOXED_BYTE = ClassName.get("java.lang", "Byte");
private static final ClassName BOXED_SHORT = ClassName.get("java.lang", "Short");
private static final ClassName BOXED_INT = ClassName.get("java.lang", "Integer");
private static final ClassName BOXED_LONG = ClassName.get("java.lang", "Long");
private static final ClassName BOXED_CHAR = ClassName.get("java.lang", "Character");
private static final ClassName BOXED_FLOAT = ClassName.get("java.lang", "Float");
private static final ClassName BOXED_DOUBLE = ClassName.get("java.lang", "Double");
/**
* The name of this type if it is a keyword, or null.
*/
private final String keyword;
public final List<AnnotationSpec> annotations;
/**
* Lazily-initialized toString of this type name.
*/
private String cachedString;
private TypeName(String keyword) {
this(keyword, new ArrayList<>());
}
private TypeName(String keyword, List<AnnotationSpec> annotations) {
this.keyword = keyword;
this.annotations = Util.immutableList(annotations);
}
// Package-private constructor to prevent third-party subclasses.
TypeName(List<AnnotationSpec> annotations) {
this(null, annotations);
}
public final TypeName annotated(AnnotationSpec... annotations) {
return annotated(Arrays.asList(annotations));
}
public TypeName annotated(List<AnnotationSpec> annotations) {
Util.checkNotNull(annotations, "annotations == null");
return new TypeName(keyword, concatAnnotations(annotations));
}
public TypeName withoutAnnotations() {
if (annotations.isEmpty()) {
return this;
}
return new TypeName(keyword);
}
protected final List<AnnotationSpec> concatAnnotations(List<AnnotationSpec> annotations) {
List<AnnotationSpec> allAnnotations = new ArrayList<>(this.annotations);
allAnnotations.addAll(annotations);
return allAnnotations;
}
public boolean isAnnotated() {
return !annotations.isEmpty();
}
/**
* Returns true if this is a primitive type like {@code int}. Returns false for all other types
* types including boxed primitives and {@code void}.
*/
public boolean isPrimitive() {
return keyword != null && this != VOID;
}
/**
* Returns true if this is a boxed primitive type like {@code Integer}. Returns false for all
* other types types including unboxed primitives and {@code java.lang.Void}.
*/
public boolean isBoxedPrimitive() {
TypeName thisWithoutAnnotations = withoutAnnotations();
return thisWithoutAnnotations.equals(BOXED_BOOLEAN)
|| thisWithoutAnnotations.equals(BOXED_BYTE)
|| thisWithoutAnnotations.equals(BOXED_SHORT)
|| thisWithoutAnnotations.equals(BOXED_INT)
|| thisWithoutAnnotations.equals(BOXED_LONG)
|| thisWithoutAnnotations.equals(BOXED_CHAR)
|| thisWithoutAnnotations.equals(BOXED_FLOAT)
|| thisWithoutAnnotations.equals(BOXED_DOUBLE);
}
/**
* Returns a boxed type if this is a primitive type (like {@code Integer} for {@code int}) or
* {@code void}. Returns this type if boxing doesn't apply.
*/
public TypeName box() {
if (keyword == null) return this; // Doesn't need boxing.
TypeName boxed = null;
if (keyword.equals(VOID.keyword)) boxed = BOXED_VOID;
else if (keyword.equals(BOOLEAN.keyword)) boxed = BOXED_BOOLEAN;
else if (keyword.equals(BYTE.keyword)) boxed = BOXED_BYTE;
else if (keyword.equals(SHORT.keyword)) boxed = BOXED_SHORT;
else if (keyword.equals(INT.keyword)) boxed = BOXED_INT;
else if (keyword.equals(LONG.keyword)) boxed = BOXED_LONG;
else if (keyword.equals(CHAR.keyword)) boxed = BOXED_CHAR;
else if (keyword.equals(FLOAT.keyword)) boxed = BOXED_FLOAT;
else if (keyword.equals(DOUBLE.keyword)) boxed = BOXED_DOUBLE;
else throw new AssertionError(keyword);
return annotations.isEmpty() ? boxed : boxed.annotated(annotations);
}
/**
* Returns an unboxed type if this is a boxed primitive type (like {@code int} for {@code
* Integer}) or {@code Void}. Returns this type if it is already unboxed.
*
* @throws UnsupportedOperationException if this type isn't eligible for unboxing.
*/
public TypeName unbox() {
if (keyword != null) return this; // Already unboxed.
TypeName thisWithoutAnnotations = withoutAnnotations();
TypeName unboxed = null;
if (thisWithoutAnnotations.equals(BOXED_VOID)) unboxed = VOID;
else if (thisWithoutAnnotations.equals(BOXED_BOOLEAN)) unboxed = BOOLEAN;
else if (thisWithoutAnnotations.equals(BOXED_BYTE)) unboxed = BYTE;
else if (thisWithoutAnnotations.equals(BOXED_SHORT)) unboxed = SHORT;
else if (thisWithoutAnnotations.equals(BOXED_INT)) unboxed = INT;
else if (thisWithoutAnnotations.equals(BOXED_LONG)) unboxed = LONG;
else if (thisWithoutAnnotations.equals(BOXED_CHAR)) unboxed = CHAR;
else if (thisWithoutAnnotations.equals(BOXED_FLOAT)) unboxed = FLOAT;
else if (thisWithoutAnnotations.equals(BOXED_DOUBLE)) unboxed = DOUBLE;
else throw new UnsupportedOperationException("cannot unbox " + this);
return annotations.isEmpty() ? unboxed : unboxed.annotated(annotations);
}
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public final int hashCode() {
return toString().hashCode();
}
@Override
public final String toString() {
String result = cachedString;
if (result == null) {
try {
StringBuilder resultBuilder = new StringBuilder();
CodeWriter codeWriter = new CodeWriter(resultBuilder);
emit(codeWriter);
result = resultBuilder.toString();
cachedString = result;
} catch (IOException e) {
throw new AssertionError();
}
}
return result;
}
CodeWriter emit(CodeWriter out) throws IOException {
if (keyword == null) throw new AssertionError();
if (isAnnotated()) {
out.emit("");
emitAnnotations(out);
}
return out.emitAndIndent(keyword);
}
CodeWriter emitAnnotations(CodeWriter out) throws IOException {
for (AnnotationSpec annotation : annotations) {
annotation.emit(out, true);
out.emit(" ");
}
return out;
}
/**
* Returns a type name equivalent to {@code mirror}.
*/
public static TypeName get(TypeMirror mirror) {
return get(mirror, new LinkedHashMap<>());
}
static TypeName get(TypeMirror mirror,
final Map<TypeParameterElement, TypeVariableName> typeVariables) {
return mirror.accept(new SimpleTypeVisitor8<TypeName, Void>() {
@Override
public TypeName visitPrimitive(PrimitiveType t, Void p) {
switch (t.getKind()) {
case BOOLEAN:
return TypeName.BOOLEAN;
case BYTE:
return TypeName.BYTE;
case SHORT:
return TypeName.SHORT;
case INT:
return TypeName.INT;
case LONG:
return TypeName.LONG;
case CHAR:
return TypeName.CHAR;
case FLOAT:
return TypeName.FLOAT;
case DOUBLE:
return TypeName.DOUBLE;
default:
throw new AssertionError();
}
}
@Override
public TypeName visitDeclared(DeclaredType t, Void p) {
ClassName rawType = ClassName.get((TypeElement) t.asElement());
TypeMirror enclosingType = t.getEnclosingType();
TypeName enclosing =
(enclosingType.getKind() != TypeKind.NONE)
&& !t.asElement().getModifiers().contains(Modifier.STATIC)
? enclosingType.accept(this, null)
: null;
if (t.getTypeArguments().isEmpty() && !(enclosing instanceof ParameterizedTypeName)) {
return rawType;
}
List<TypeName> typeArgumentNames = new ArrayList<>();
for (TypeMirror mirror : t.getTypeArguments()) {
typeArgumentNames.add(get(mirror, typeVariables));
}
return enclosing instanceof ParameterizedTypeName
? ((ParameterizedTypeName) enclosing).nestedClass(
rawType.simpleName(), typeArgumentNames)
: new ParameterizedTypeName(null, rawType, typeArgumentNames);
}
@Override
public TypeName visitError(ErrorType t, Void p) {
return visitDeclared(t, p);
}
@Override
public ArrayTypeName visitArray(ArrayType t, Void p) {
return ArrayTypeName.get(t, typeVariables);
}
@Override
public TypeName visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) {
return TypeVariableName.get(t, typeVariables);
}
@Override
public TypeName visitWildcard(javax.lang.model.type.WildcardType t, Void p) {
return WildcardTypeName.get(t, typeVariables);
}
@Override
public TypeName visitNoType(NoType t, Void p) {
if (t.getKind() == TypeKind.VOID) return TypeName.VOID;
return super.visitUnknown(t, p);
}
@Override
protected TypeName defaultAction(TypeMirror e, Void p) {
throw new IllegalArgumentException("Unexpected type mirror: " + e);
}
}, null);
}
/**
* Returns a type name equivalent to {@code type}.
*/
public static TypeName get(Type type) {
return get(type, new LinkedHashMap<>());
}
static TypeName get(Type type, Map<Type, TypeVariableName> map) {
if (type instanceof Class<?>) {
Class<?> classType = (Class<?>) type;
if (type == void.class) return VOID;
if (type == boolean.class) return BOOLEAN;
if (type == byte.class) return BYTE;
if (type == short.class) return SHORT;
if (type == int.class) return INT;
if (type == long.class) return LONG;
if (type == char.class) return CHAR;
if (type == float.class) return FLOAT;
if (type == double.class) return DOUBLE;
if (classType.isArray()) return ArrayTypeName.of(get(classType.getComponentType(), map));
return ClassName.get(classType);
} else if (type instanceof ParameterizedType) {
return ParameterizedTypeName.get((ParameterizedType) type, map);
} else if (type instanceof WildcardType) {
return WildcardTypeName.get((WildcardType) type, map);
} else if (type instanceof TypeVariable<?>) {
return TypeVariableName.get((TypeVariable<?>) type, map);
} else if (type instanceof GenericArrayType) {
return ArrayTypeName.get((GenericArrayType) type, map);
} else {
throw new IllegalArgumentException("unexpected type: " + type);
}
}
/**
* Converts an array of types to a list of type names.
*/
static List<TypeName> list(Type[] types) {
return list(types, new LinkedHashMap<>());
}
static List<TypeName> list(Type[] types, Map<Type, TypeVariableName> map) {
List<TypeName> result = new ArrayList<>(types.length);
for (Type type : types) {
result.add(get(type, map));
}
return result;
}
/**
* Returns the array component of {@code type}, or null if {@code type} is not an array.
*/
static TypeName arrayComponent(TypeName type) {
return type instanceof ArrayTypeName
? ((ArrayTypeName) type).componentType
: null;
}
/**
* Returns {@code type} as an array, or null if {@code type} is not an array.
*/
static ArrayTypeName asArray(TypeName type) {
return type instanceof ArrayTypeName
? ((ArrayTypeName) type)
: null;
}
}

View file

@ -0,0 +1,832 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
import static org.xbib.javapoet.Util.checkState;
import static org.xbib.javapoet.Util.requireExactlyOneOf;
/**
* A generated class, interface, or enum declaration.
*/
public final class TypeSpec {
public final Kind kind;
public final String name;
public final CodeBlock anonymousTypeArguments;
public final CodeBlock javadoc;
public final List<AnnotationSpec> annotations;
public final Set<Modifier> modifiers;
public final List<TypeVariableName> typeVariables;
public final TypeName superclass;
public final List<TypeName> superinterfaces;
public final Map<String, TypeSpec> enumConstants;
public final List<FieldSpec> fieldSpecs;
public final CodeBlock staticBlock;
public final CodeBlock initializerBlock;
public final List<MethodSpec> methodSpecs;
public final List<TypeSpec> typeSpecs;
final Set<String> nestedTypesSimpleNames;
public final List<Element> originatingElements;
public final Set<String> alwaysQualifiedNames;
private TypeSpec(Builder builder) {
this.kind = builder.kind;
this.name = builder.name;
this.anonymousTypeArguments = builder.anonymousTypeArguments;
this.javadoc = builder.javadoc.build();
this.annotations = Util.immutableList(builder.annotations);
this.modifiers = Util.immutableSet(builder.modifiers);
this.typeVariables = Util.immutableList(builder.typeVariables);
this.superclass = builder.superclass;
this.superinterfaces = Util.immutableList(builder.superinterfaces);
this.enumConstants = Util.immutableMap(builder.enumConstants);
this.fieldSpecs = Util.immutableList(builder.fieldSpecs);
this.staticBlock = builder.staticBlock.build();
this.initializerBlock = builder.initializerBlock.build();
this.methodSpecs = Util.immutableList(builder.methodSpecs);
this.typeSpecs = Util.immutableList(builder.typeSpecs);
this.alwaysQualifiedNames = Util.immutableSet(builder.alwaysQualifiedNames);
nestedTypesSimpleNames = new HashSet<>(builder.typeSpecs.size());
List<Element> originatingElementsMutable = new ArrayList<>();
originatingElementsMutable.addAll(builder.originatingElements);
for (TypeSpec typeSpec : builder.typeSpecs) {
nestedTypesSimpleNames.add(typeSpec.name);
originatingElementsMutable.addAll(typeSpec.originatingElements);
}
this.originatingElements = Util.immutableList(originatingElementsMutable);
}
/**
* Creates a dummy type spec for type-resolution only (in CodeWriter)
* while emitting the type declaration but before entering the type body.
*/
private TypeSpec(TypeSpec type) {
assert type.anonymousTypeArguments == null;
this.kind = type.kind;
this.name = type.name;
this.anonymousTypeArguments = null;
this.javadoc = type.javadoc;
this.annotations = Collections.emptyList();
this.modifiers = Collections.emptySet();
this.typeVariables = Collections.emptyList();
this.superclass = null;
this.superinterfaces = Collections.emptyList();
this.enumConstants = Collections.emptyMap();
this.fieldSpecs = Collections.emptyList();
this.staticBlock = type.staticBlock;
this.initializerBlock = type.initializerBlock;
this.methodSpecs = Collections.emptyList();
this.typeSpecs = Collections.emptyList();
this.originatingElements = Collections.emptyList();
this.nestedTypesSimpleNames = Collections.emptySet();
this.alwaysQualifiedNames = Collections.emptySet();
}
public boolean hasModifier(Modifier modifier) {
return modifiers.contains(modifier);
}
public static Builder classBuilder(String name) {
return new Builder(Kind.CLASS, checkNotNull(name, "name == null"), null);
}
public static Builder classBuilder(ClassName className) {
return classBuilder(checkNotNull(className, "className == null").simpleName());
}
public static Builder interfaceBuilder(String name) {
return new Builder(Kind.INTERFACE, checkNotNull(name, "name == null"), null);
}
public static Builder interfaceBuilder(ClassName className) {
return interfaceBuilder(checkNotNull(className, "className == null").simpleName());
}
public static Builder enumBuilder(String name) {
return new Builder(Kind.ENUM, checkNotNull(name, "name == null"), null);
}
public static Builder enumBuilder(ClassName className) {
return enumBuilder(checkNotNull(className, "className == null").simpleName());
}
public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) {
return anonymousClassBuilder(CodeBlock.of(typeArgumentsFormat, args));
}
public static Builder anonymousClassBuilder(CodeBlock typeArguments) {
return new Builder(Kind.CLASS, null, typeArguments);
}
public static Builder annotationBuilder(String name) {
return new Builder(Kind.ANNOTATION, checkNotNull(name, "name == null"), null);
}
public static Builder annotationBuilder(ClassName className) {
return annotationBuilder(checkNotNull(className, "className == null").simpleName());
}
public Builder toBuilder() {
Builder builder = new Builder(kind, name, anonymousTypeArguments);
builder.javadoc.add(javadoc);
builder.annotations.addAll(annotations);
builder.modifiers.addAll(modifiers);
builder.typeVariables.addAll(typeVariables);
builder.superclass = superclass;
builder.superinterfaces.addAll(superinterfaces);
builder.enumConstants.putAll(enumConstants);
builder.fieldSpecs.addAll(fieldSpecs);
builder.methodSpecs.addAll(methodSpecs);
builder.typeSpecs.addAll(typeSpecs);
builder.initializerBlock.add(initializerBlock);
builder.staticBlock.add(staticBlock);
builder.originatingElements.addAll(originatingElements);
builder.alwaysQualifiedNames.addAll(alwaysQualifiedNames);
return builder;
}
void emit(CodeWriter codeWriter, String enumName, Set<Modifier> implicitModifiers)
throws IOException {
// Nested classes interrupt wrapped line indentation. Stash the current wrapping state and put
// it back afterwards when this type is complete.
int previousStatementLine = codeWriter.statementLine;
codeWriter.statementLine = -1;
try {
if (enumName != null) {
codeWriter.emitJavadoc(javadoc);
codeWriter.emitAnnotations(annotations, false);
codeWriter.emit("$L", enumName);
if (!anonymousTypeArguments.formatParts.isEmpty()) {
codeWriter.emit("(");
codeWriter.emit(anonymousTypeArguments);
codeWriter.emit(")");
}
if (fieldSpecs.isEmpty() && methodSpecs.isEmpty() && typeSpecs.isEmpty()) {
return; // Avoid unnecessary braces "{}".
}
codeWriter.emit(" {\n");
} else if (anonymousTypeArguments != null) {
TypeName supertype = !superinterfaces.isEmpty() ? superinterfaces.get(0) : superclass;
codeWriter.emit("new $T(", supertype);
codeWriter.emit(anonymousTypeArguments);
codeWriter.emit(") {\n");
} else {
// Push an empty type (specifically without nested types) for type-resolution.
codeWriter.pushType(new TypeSpec(this));
codeWriter.emitJavadoc(javadoc);
codeWriter.emitAnnotations(annotations, false);
codeWriter.emitModifiers(modifiers, Util.union(implicitModifiers, kind.asMemberModifiers));
if (kind == Kind.ANNOTATION) {
codeWriter.emit("$L $L", "@interface", name);
} else {
codeWriter.emit("$L $L", kind.name().toLowerCase(Locale.US), name);
}
codeWriter.emitTypeVariables(typeVariables);
List<TypeName> extendsTypes;
List<TypeName> implementsTypes;
if (kind == Kind.INTERFACE) {
extendsTypes = superinterfaces;
implementsTypes = Collections.emptyList();
} else {
extendsTypes = superclass.equals(ClassName.OBJECT)
? Collections.emptyList()
: Collections.singletonList(superclass);
implementsTypes = superinterfaces;
}
if (!extendsTypes.isEmpty()) {
codeWriter.emit(" extends");
boolean firstType = true;
for (TypeName type : extendsTypes) {
if (!firstType) codeWriter.emit(",");
codeWriter.emit(" $T", type);
firstType = false;
}
}
if (!implementsTypes.isEmpty()) {
codeWriter.emit(" implements");
boolean firstType = true;
for (TypeName type : implementsTypes) {
if (!firstType) codeWriter.emit(",");
codeWriter.emit(" $T", type);
firstType = false;
}
}
codeWriter.popType();
codeWriter.emit(" {\n");
}
codeWriter.pushType(this);
codeWriter.indent();
boolean firstMember = true;
boolean needsSeparator = kind == Kind.ENUM
&& (!fieldSpecs.isEmpty() || !methodSpecs.isEmpty() || !typeSpecs.isEmpty());
for (Iterator<Map.Entry<String, TypeSpec>> i = enumConstants.entrySet().iterator();
i.hasNext(); ) {
Map.Entry<String, TypeSpec> enumConstant = i.next();
if (!firstMember) codeWriter.emit("\n");
enumConstant.getValue().emit(codeWriter, enumConstant.getKey(), Collections.emptySet());
firstMember = false;
if (i.hasNext()) {
codeWriter.emit(",\n");
} else if (!needsSeparator) {
codeWriter.emit("\n");
}
}
if (needsSeparator) codeWriter.emit(";\n");
// Static fields.
for (FieldSpec fieldSpec : fieldSpecs) {
if (!fieldSpec.hasModifier(Modifier.STATIC)) continue;
if (!firstMember) codeWriter.emit("\n");
fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
firstMember = false;
}
if (!staticBlock.isEmpty()) {
if (!firstMember) codeWriter.emit("\n");
codeWriter.emit(staticBlock);
firstMember = false;
}
// Non-static fields.
for (FieldSpec fieldSpec : fieldSpecs) {
if (fieldSpec.hasModifier(Modifier.STATIC)) continue;
if (!firstMember) codeWriter.emit("\n");
fieldSpec.emit(codeWriter, kind.implicitFieldModifiers);
firstMember = false;
}
// Initializer block.
if (!initializerBlock.isEmpty()) {
if (!firstMember) codeWriter.emit("\n");
codeWriter.emit(initializerBlock);
firstMember = false;
}
// Constructors.
for (MethodSpec methodSpec : methodSpecs) {
if (!methodSpec.isConstructor()) continue;
if (!firstMember) codeWriter.emit("\n");
methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
firstMember = false;
}
// Methods (static and non-static).
for (MethodSpec methodSpec : methodSpecs) {
if (methodSpec.isConstructor()) continue;
if (!firstMember) codeWriter.emit("\n");
methodSpec.emit(codeWriter, name, kind.implicitMethodModifiers);
firstMember = false;
}
// Types.
for (TypeSpec typeSpec : typeSpecs) {
if (!firstMember) codeWriter.emit("\n");
typeSpec.emit(codeWriter, null, kind.implicitTypeModifiers);
firstMember = false;
}
codeWriter.unindent();
codeWriter.popType();
codeWriter.popTypeVariables(typeVariables);
codeWriter.emit("}");
if (enumName == null && anonymousTypeArguments == null) {
codeWriter.emit("\n"); // If this type isn't also a value, include a trailing newline.
}
} finally {
codeWriter.statementLine = previousStatementLine;
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (getClass() != o.getClass()) return false;
return toString().equals(o.toString());
}
@Override
public int hashCode() {
return toString().hashCode();
}
@Override
public String toString() {
StringBuilder out = new StringBuilder();
try {
CodeWriter codeWriter = new CodeWriter(out);
emit(codeWriter, null, Collections.emptySet());
return out.toString();
} catch (IOException e) {
throw new AssertionError();
}
}
public enum Kind {
CLASS(
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet()),
INTERFACE(
Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)),
Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)),
Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)),
Util.immutableSet(Collections.singletonList(Modifier.STATIC))),
ENUM(
Collections.emptySet(),
Collections.emptySet(),
Collections.emptySet(),
Collections.singleton(Modifier.STATIC)),
ANNOTATION(
Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)),
Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.ABSTRACT)),
Util.immutableSet(Arrays.asList(Modifier.PUBLIC, Modifier.STATIC)),
Util.immutableSet(Collections.singletonList(Modifier.STATIC)));
private final Set<Modifier> implicitFieldModifiers;
private final Set<Modifier> implicitMethodModifiers;
private final Set<Modifier> implicitTypeModifiers;
private final Set<Modifier> asMemberModifiers;
Kind(Set<Modifier> implicitFieldModifiers,
Set<Modifier> implicitMethodModifiers,
Set<Modifier> implicitTypeModifiers,
Set<Modifier> asMemberModifiers) {
this.implicitFieldModifiers = implicitFieldModifiers;
this.implicitMethodModifiers = implicitMethodModifiers;
this.implicitTypeModifiers = implicitTypeModifiers;
this.asMemberModifiers = asMemberModifiers;
}
}
public static final class Builder {
private final Kind kind;
private final String name;
private final CodeBlock anonymousTypeArguments;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
private TypeName superclass = ClassName.OBJECT;
private final CodeBlock.Builder staticBlock = CodeBlock.builder();
private final CodeBlock.Builder initializerBlock = CodeBlock.builder();
public final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>();
public final List<AnnotationSpec> annotations = new ArrayList<>();
public final List<Modifier> modifiers = new ArrayList<>();
public final List<TypeVariableName> typeVariables = new ArrayList<>();
public final List<TypeName> superinterfaces = new ArrayList<>();
public final List<FieldSpec> fieldSpecs = new ArrayList<>();
public final List<MethodSpec> methodSpecs = new ArrayList<>();
public final List<TypeSpec> typeSpecs = new ArrayList<>();
public final List<Element> originatingElements = new ArrayList<>();
public final Set<String> alwaysQualifiedNames = new LinkedHashSet<>();
private Builder(Kind kind, String name,
CodeBlock anonymousTypeArguments) {
checkArgument(name == null || SourceVersion.isName(name), "not a valid name: %s", name);
this.kind = kind;
this.name = name;
this.anonymousTypeArguments = anonymousTypeArguments;
}
public Builder addJavadoc(String format, Object... args) {
javadoc.add(format, args);
return this;
}
public Builder addJavadoc(CodeBlock block) {
javadoc.add(block);
return this;
}
public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
checkArgument(annotationSpecs != null, "annotationSpecs == null");
for (AnnotationSpec annotationSpec : annotationSpecs) {
this.annotations.add(annotationSpec);
}
return this;
}
public Builder addAnnotation(AnnotationSpec annotationSpec) {
checkNotNull(annotationSpec, "annotationSpec == null");
this.annotations.add(annotationSpec);
return this;
}
public Builder addAnnotation(ClassName annotation) {
return addAnnotation(AnnotationSpec.builder(annotation).build());
}
public Builder addAnnotation(Class<?> annotation) {
return addAnnotation(ClassName.get(annotation));
}
public Builder addModifiers(Modifier... modifiers) {
Collections.addAll(this.modifiers, modifiers);
return this;
}
public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
checkArgument(typeVariables != null, "typeVariables == null");
for (TypeVariableName typeVariable : typeVariables) {
this.typeVariables.add(typeVariable);
}
return this;
}
public Builder addTypeVariable(TypeVariableName typeVariable) {
typeVariables.add(typeVariable);
return this;
}
public Builder superclass(TypeName superclass) {
checkState(this.kind == Kind.CLASS, "only classes have super classes, not " + this.kind);
checkState(this.superclass == ClassName.OBJECT,
"superclass already set to " + this.superclass);
checkArgument(!superclass.isPrimitive(), "superclass may not be a primitive");
this.superclass = superclass;
return this;
}
public Builder superclass(Type superclass) {
return superclass(superclass, true);
}
public Builder superclass(Type superclass, boolean avoidNestedTypeNameClashes) {
superclass(TypeName.get(superclass));
if (avoidNestedTypeNameClashes) {
Class<?> clazz = getRawType(superclass);
if (clazz != null) {
avoidClashesWithNestedClasses(clazz);
}
}
return this;
}
public Builder superclass(TypeMirror superclass) {
return superclass(superclass, true);
}
public Builder superclass(TypeMirror superclass, boolean avoidNestedTypeNameClashes) {
superclass(TypeName.get(superclass));
if (avoidNestedTypeNameClashes && superclass instanceof DeclaredType) {
TypeElement superInterfaceElement =
(TypeElement) ((DeclaredType) superclass).asElement();
avoidClashesWithNestedClasses(superInterfaceElement);
}
return this;
}
public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) {
checkArgument(superinterfaces != null, "superinterfaces == null");
for (TypeName superinterface : superinterfaces) {
addSuperinterface(superinterface);
}
return this;
}
public Builder addSuperinterface(TypeName superinterface) {
checkArgument(superinterface != null, "superinterface == null");
this.superinterfaces.add(superinterface);
return this;
}
public Builder addSuperinterface(Type superinterface) {
return addSuperinterface(superinterface, true);
}
public Builder addSuperinterface(Type superinterface, boolean avoidNestedTypeNameClashes) {
addSuperinterface(TypeName.get(superinterface));
if (avoidNestedTypeNameClashes) {
Class<?> clazz = getRawType(superinterface);
if (clazz != null) {
avoidClashesWithNestedClasses(clazz);
}
}
return this;
}
private Class<?> getRawType(Type type) {
if (type instanceof Class<?>) {
return (Class<?>) type;
} else if (type instanceof ParameterizedType) {
return getRawType(((ParameterizedType) type).getRawType());
} else {
return null;
}
}
public Builder addSuperinterface(TypeMirror superinterface) {
return addSuperinterface(superinterface, true);
}
public Builder addSuperinterface(TypeMirror superinterface,
boolean avoidNestedTypeNameClashes) {
addSuperinterface(TypeName.get(superinterface));
if (avoidNestedTypeNameClashes && superinterface instanceof DeclaredType) {
TypeElement superInterfaceElement =
(TypeElement) ((DeclaredType) superinterface).asElement();
avoidClashesWithNestedClasses(superInterfaceElement);
}
return this;
}
public Builder addEnumConstant(String name) {
return addEnumConstant(name, anonymousClassBuilder("").build());
}
public Builder addEnumConstant(String name, TypeSpec typeSpec) {
enumConstants.put(name, typeSpec);
return this;
}
public Builder addFields(Iterable<FieldSpec> fieldSpecs) {
checkArgument(fieldSpecs != null, "fieldSpecs == null");
for (FieldSpec fieldSpec : fieldSpecs) {
addField(fieldSpec);
}
return this;
}
public Builder addField(FieldSpec fieldSpec) {
fieldSpecs.add(fieldSpec);
return this;
}
public Builder addField(TypeName type, String name, Modifier... modifiers) {
return addField(FieldSpec.builder(type, name, modifiers).build());
}
public Builder addField(Type type, String name, Modifier... modifiers) {
return addField(TypeName.get(type), name, modifiers);
}
public Builder addStaticBlock(CodeBlock block) {
staticBlock.beginControlFlow("static").add(block).endControlFlow();
return this;
}
public Builder addInitializerBlock(CodeBlock block) {
if ((kind != Kind.CLASS && kind != Kind.ENUM)) {
throw new UnsupportedOperationException(kind + " can't have initializer blocks");
}
initializerBlock.add("{\n")
.indent()
.add(block)
.unindent()
.add("}\n");
return this;
}
public Builder addMethods(Iterable<MethodSpec> methodSpecs) {
checkArgument(methodSpecs != null, "methodSpecs == null");
for (MethodSpec methodSpec : methodSpecs) {
addMethod(methodSpec);
}
return this;
}
public Builder addMethod(MethodSpec methodSpec) {
methodSpecs.add(methodSpec);
return this;
}
public Builder addTypes(Iterable<TypeSpec> typeSpecs) {
checkArgument(typeSpecs != null, "typeSpecs == null");
for (TypeSpec typeSpec : typeSpecs) {
addType(typeSpec);
}
return this;
}
public Builder addType(TypeSpec typeSpec) {
typeSpecs.add(typeSpec);
return this;
}
public Builder addOriginatingElement(Element originatingElement) {
originatingElements.add(originatingElement);
return this;
}
public Builder alwaysQualify(String... simpleNames) {
checkArgument(simpleNames != null, "simpleNames == null");
for (String name : simpleNames) {
checkArgument(
name != null,
"null entry in simpleNames array: %s",
Arrays.toString(simpleNames)
);
alwaysQualifiedNames.add(name);
}
return this;
}
/**
* Call this to always fully qualify any types that would conflict with possibly nested types of
* this {@code typeElement}. For example - if the following type was passed in as the
* typeElement:
*
* <pre><code>
* class Foo {
* class NestedTypeA {
*
* }
* class NestedTypeB {
*
* }
* }
* </code></pre>
*
* <p>
* Then this would add {@code "NestedTypeA"} and {@code "NestedTypeB"} as names that should
* always be qualified via {@link #alwaysQualify(String...)}. This way they would avoid
* possible import conflicts when this JavaFile is written.
*
* @param typeElement the {@link TypeElement} with nested types to avoid clashes with.
* @return this builder instance.
*/
public Builder avoidClashesWithNestedClasses(TypeElement typeElement) {
checkArgument(typeElement != null, "typeElement == null");
for (TypeElement nestedType : ElementFilter.typesIn(typeElement.getEnclosedElements())) {
alwaysQualify(nestedType.getSimpleName().toString());
}
TypeMirror superclass = typeElement.getSuperclass();
if (!(superclass instanceof NoType) && superclass instanceof DeclaredType) {
TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement();
avoidClashesWithNestedClasses(superclassElement);
}
for (TypeMirror superinterface : typeElement.getInterfaces()) {
if (superinterface instanceof DeclaredType) {
TypeElement superinterfaceElement
= (TypeElement) ((DeclaredType) superinterface).asElement();
avoidClashesWithNestedClasses(superinterfaceElement);
}
}
return this;
}
/**
* Call this to always fully qualify any types that would conflict with possibly nested types of
* this {@code typeElement}. For example - if the following type was passed in as the
* typeElement:
*
* <pre><code>
* class Foo {
* class NestedTypeA {
*
* }
* class NestedTypeB {
*
* }
* }
* </code></pre>
*
* <p>
* Then this would add {@code "NestedTypeA"} and {@code "NestedTypeB"} as names that should
* always be qualified via {@link #alwaysQualify(String...)}. This way they would avoid
* possible import conflicts when this JavaFile is written.
*
* @param clazz the {@link Class} with nested types to avoid clashes with.
* @return this builder instance.
*/
public Builder avoidClashesWithNestedClasses(Class<?> clazz) {
checkArgument(clazz != null, "clazz == null");
for (Class<?> nestedType : clazz.getDeclaredClasses()) {
alwaysQualify(nestedType.getSimpleName());
}
Class<?> superclass = clazz.getSuperclass();
if (superclass != null && !Object.class.equals(superclass)) {
avoidClashesWithNestedClasses(superclass);
}
for (Class<?> superinterface : clazz.getInterfaces()) {
avoidClashesWithNestedClasses(superinterface);
}
return this;
}
public TypeSpec build() {
for (AnnotationSpec annotationSpec : annotations) {
checkNotNull(annotationSpec, "annotationSpec == null");
}
if (!modifiers.isEmpty()) {
checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
for (Modifier modifier : modifiers) {
checkArgument(modifier != null, "modifiers contain null");
}
}
for (TypeName superinterface : superinterfaces) {
checkArgument(superinterface != null, "superinterfaces contains null");
}
if (!typeVariables.isEmpty()) {
checkState(anonymousTypeArguments == null,
"typevariables are forbidden on anonymous types.");
for (TypeVariableName typeVariableName : typeVariables) {
checkArgument(typeVariableName != null, "typeVariables contain null");
}
}
for (Map.Entry<String, TypeSpec> enumConstant : enumConstants.entrySet()) {
checkState(kind == Kind.ENUM, "%s is not enum", this.name);
checkArgument(enumConstant.getValue().anonymousTypeArguments != null,
"enum constants must have anonymous type arguments");
checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name);
}
for (FieldSpec fieldSpec : fieldSpecs) {
if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) {
requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL);
checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s",
kind, name, fieldSpec.name, check);
}
}
for (MethodSpec methodSpec : methodSpecs) {
if (kind == Kind.INTERFACE) {
requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
if (methodSpec.modifiers.contains(Modifier.PRIVATE)) {
checkState(!methodSpec.hasModifier(Modifier.DEFAULT),
"%s %s.%s cannot be private and default", kind, name, methodSpec.name);
checkState(!methodSpec.hasModifier(Modifier.ABSTRACT),
"%s %s.%s cannot be private and abstract", kind, name, methodSpec.name);
} else {
requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC,
Modifier.DEFAULT);
}
} else if (kind == Kind.ANNOTATION) {
checkState(methodSpec.modifiers.equals(kind.implicitMethodModifiers),
"%s %s.%s requires modifiers %s",
kind, name, methodSpec.name, kind.implicitMethodModifiers);
}
if (kind != Kind.ANNOTATION) {
checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value",
kind, name, methodSpec.name);
}
if (kind != Kind.INTERFACE) {
checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default",
kind, name, methodSpec.name);
}
}
for (TypeSpec typeSpec : typeSpecs) {
checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
"%s %s.%s requires modifiers %s", kind, name, typeSpec.name,
kind.implicitTypeModifiers);
}
boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || kind != Kind.CLASS;
for (MethodSpec methodSpec : methodSpecs) {
checkArgument(isAbstract || !methodSpec.hasModifier(Modifier.ABSTRACT),
"non-abstract type %s cannot declare abstract method %s", name, methodSpec.name);
}
boolean superclassIsObject = superclass.equals(ClassName.OBJECT);
int interestingSupertypeCount = (superclassIsObject ? 0 : 1) + superinterfaces.size();
checkArgument(anonymousTypeArguments == null || interestingSupertypeCount <= 1,
"anonymous type has too many supertypes");
return new TypeSpec(this);
}
}
}

View file

@ -0,0 +1,168 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import static org.xbib.javapoet.Util.checkArgument;
import static org.xbib.javapoet.Util.checkNotNull;
public final class TypeVariableName extends TypeName {
public final String name;
public final List<TypeName> bounds;
private TypeVariableName(String name, List<TypeName> bounds) {
this(name, bounds, new ArrayList<>());
}
private TypeVariableName(String name, List<TypeName> bounds, List<AnnotationSpec> annotations) {
super(annotations);
this.name = checkNotNull(name, "name == null");
this.bounds = bounds;
for (TypeName bound : this.bounds) {
checkArgument(!bound.isPrimitive() && bound != VOID, "invalid bound: %s", bound);
}
}
@Override
public TypeVariableName annotated(List<AnnotationSpec> annotations) {
return new TypeVariableName(name, bounds, annotations);
}
@Override
public TypeName withoutAnnotations() {
return new TypeVariableName(name, bounds);
}
public TypeVariableName withBounds(Type... bounds) {
return withBounds(TypeName.list(bounds));
}
public TypeVariableName withBounds(TypeName... bounds) {
return withBounds(Arrays.asList(bounds));
}
public TypeVariableName withBounds(List<? extends TypeName> bounds) {
ArrayList<TypeName> newBounds = new ArrayList<>();
newBounds.addAll(this.bounds);
newBounds.addAll(bounds);
return new TypeVariableName(name, newBounds, annotations);
}
private static TypeVariableName of(String name, List<TypeName> bounds) {
// Strip java.lang.Object from bounds if it is present.
List<TypeName> boundsNoObject = new ArrayList<>(bounds);
boundsNoObject.remove(OBJECT);
return new TypeVariableName(name, Collections.unmodifiableList(boundsNoObject));
}
@Override
CodeWriter emit(CodeWriter out) throws IOException {
emitAnnotations(out);
return out.emitAndIndent(name);
}
/**
* Returns type variable named {@code name} without bounds.
*/
public static TypeVariableName get(String name) {
return TypeVariableName.of(name, Collections.emptyList());
}
/**
* Returns type variable named {@code name} with {@code bounds}.
*/
public static TypeVariableName get(String name, TypeName... bounds) {
return TypeVariableName.of(name, Arrays.asList(bounds));
}
/**
* Returns type variable named {@code name} with {@code bounds}.
*/
public static TypeVariableName get(String name, Type... bounds) {
return TypeVariableName.of(name, TypeName.list(bounds));
}
/**
* Returns type variable equivalent to {@code mirror}.
*/
public static TypeVariableName get(TypeVariable mirror) {
return get((TypeParameterElement) mirror.asElement());
}
/**
* Make a TypeVariableName for the given TypeMirror. This form is used internally to avoid
* infinite recursion in cases like {@code Enum<E extends Enum<E>>}. When we encounter such a
* thing, we will make a TypeVariableName without bounds and add that to the {@code typeVariables}
* map before looking up the bounds. Then if we encounter this TypeVariable again while
* constructing the bounds, we can just return it from the map. And, the code that put the entry
* in {@code variables} will make sure that the bounds are filled in before returning.
*/
static TypeVariableName get(
TypeVariable mirror, Map<TypeParameterElement, TypeVariableName> typeVariables) {
TypeParameterElement element = (TypeParameterElement) mirror.asElement();
TypeVariableName typeVariableName = typeVariables.get(element);
if (typeVariableName == null) {
// Since the bounds field is public, we need to make it an unmodifiableList. But we control
// the List that that wraps, which means we can change it before returning.
List<TypeName> bounds = new ArrayList<>();
List<TypeName> visibleBounds = Collections.unmodifiableList(bounds);
typeVariableName = new TypeVariableName(element.getSimpleName().toString(), visibleBounds);
typeVariables.put(element, typeVariableName);
for (TypeMirror typeMirror : element.getBounds()) {
bounds.add(TypeName.get(typeMirror, typeVariables));
}
bounds.remove(OBJECT);
}
return typeVariableName;
}
/**
* Returns type variable equivalent to {@code element}.
*/
public static TypeVariableName get(TypeParameterElement element) {
String name = element.getSimpleName().toString();
List<? extends TypeMirror> boundsMirrors = element.getBounds();
List<TypeName> boundsTypeNames = new ArrayList<>();
for (TypeMirror typeMirror : boundsMirrors) {
boundsTypeNames.add(TypeName.get(typeMirror));
}
return TypeVariableName.of(name, boundsTypeNames);
}
/**
* Returns type variable equivalent to {@code type}.
*/
public static TypeVariableName get(java.lang.reflect.TypeVariable<?> type) {
return get(type, new LinkedHashMap<>());
}
/**
* @see #get(java.lang.reflect.TypeVariable, Map)
*/
static TypeVariableName get(java.lang.reflect.TypeVariable<?> type,
Map<Type, TypeVariableName> map) {
TypeVariableName result = map.get(type);
if (result == null) {
List<TypeName> bounds = new ArrayList<>();
List<TypeName> visibleBounds = Collections.unmodifiableList(bounds);
result = new TypeVariableName(type.getName(), visibleBounds);
map.put(type, result);
for (Type bound : type.getBounds()) {
bounds.add(TypeName.get(bound, map));
}
bounds.remove(OBJECT);
}
return result;
}
}

View file

@ -0,0 +1,125 @@
package org.xbib.javapoet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.lang.model.element.Modifier;
import static java.lang.Character.isISOControl;
/**
* Like Guava, but worse and standalone. This makes it easier to mix JavaPoet with libraries that
* bring their own version of Guava.
*/
final class Util {
private Util() {
}
static <K, V> Map<K, List<V>> immutableMultimap(Map<K, List<V>> multimap) {
LinkedHashMap<K, List<V>> result = new LinkedHashMap<>();
for (Map.Entry<K, List<V>> entry : multimap.entrySet()) {
if (entry.getValue().isEmpty()) continue;
result.put(entry.getKey(), immutableList(entry.getValue()));
}
return Collections.unmodifiableMap(result);
}
static <K, V> Map<K, V> immutableMap(Map<K, V> map) {
return Collections.unmodifiableMap(new LinkedHashMap<>(map));
}
static void checkArgument(boolean condition, String format, Object... args) {
if (!condition) throw new IllegalArgumentException(String.format(format, args));
}
static <T> T checkNotNull(T reference, String format, Object... args) {
if (reference == null) throw new NullPointerException(String.format(format, args));
return reference;
}
static void checkState(boolean condition, String format, Object... args) {
if (!condition) throw new IllegalStateException(String.format(format, args));
}
static <T> List<T> immutableList(Collection<T> collection) {
return Collections.unmodifiableList(new ArrayList<>(collection));
}
static <T> Set<T> immutableSet(Collection<T> set) {
return Collections.unmodifiableSet(new LinkedHashSet<>(set));
}
static <T> Set<T> union(Set<T> a, Set<T> b) {
Set<T> result = new LinkedHashSet<>();
result.addAll(a);
result.addAll(b);
return result;
}
static void requireExactlyOneOf(Set<Modifier> modifiers, Modifier... mutuallyExclusive) {
int count = 0;
for (Modifier modifier : mutuallyExclusive) {
if (modifiers.contains(modifier)) count++;
}
checkArgument(count == 1, "modifiers %s must contain one of %s",
modifiers, Arrays.toString(mutuallyExclusive));
}
static String characterLiteralWithoutSingleQuotes(char c) {
// see https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.6
switch (c) {
case '\b':
return "\\b"; /* \u0008: backspace (BS) */
case '\t':
return "\\t"; /* \u0009: horizontal tab (HT) */
case '\n':
return "\\n"; /* \u000a: linefeed (LF) */
case '\f':
return "\\f"; /* \u000c: form feed (FF) */
case '\r':
return "\\r"; /* \u000d: carriage return (CR) */
case '\"':
return "\""; /* \u0022: double quote (") */
case '\'':
return "\\'"; /* \u0027: single quote (') */
case '\\':
return "\\\\"; /* \u005c: backslash (\) */
default:
return isISOControl(c) ? String.format("\\u%04x", (int) c) : Character.toString(c);
}
}
/**
* Returns the string literal representing {@code value}, including wrapping double quotes.
*/
static String stringLiteralWithDoubleQuotes(String value, String indent) {
StringBuilder result = new StringBuilder(value.length() + 2);
result.append('"');
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
// trivial case: single quote must not be escaped
if (c == '\'') {
result.append("'");
continue;
}
// trivial case: double quotes must be escaped
if (c == '\"') {
result.append("\\\"");
continue;
}
// default case: just let character literal do its work
result.append(characterLiteralWithoutSingleQuotes(c));
// need to append indent after linefeed?
if (c == '\n' && i + 1 < value.length()) {
result.append("\"\n").append(indent).append(indent).append("+ \"");
}
}
result.append('"');
return result.toString();
}
}

View file

@ -0,0 +1,116 @@
package org.xbib.javapoet;
import java.io.IOException;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.TypeMirror;
import static org.xbib.javapoet.Util.checkArgument;
public final class WildcardTypeName extends TypeName {
public final List<TypeName> upperBounds;
public final List<TypeName> lowerBounds;
private WildcardTypeName(List<TypeName> upperBounds, List<TypeName> lowerBounds) {
this(upperBounds, lowerBounds, new ArrayList<>());
}
private WildcardTypeName(List<TypeName> upperBounds, List<TypeName> lowerBounds,
List<AnnotationSpec> annotations) {
super(annotations);
this.upperBounds = Util.immutableList(upperBounds);
this.lowerBounds = Util.immutableList(lowerBounds);
checkArgument(this.upperBounds.size() == 1, "unexpected extends bounds: %s", upperBounds);
for (TypeName upperBound : this.upperBounds) {
checkArgument(!upperBound.isPrimitive() && upperBound != VOID,
"invalid upper bound: %s", upperBound);
}
for (TypeName lowerBound : this.lowerBounds) {
checkArgument(!lowerBound.isPrimitive() && lowerBound != VOID,
"invalid lower bound: %s", lowerBound);
}
}
@Override
public WildcardTypeName annotated(List<AnnotationSpec> annotations) {
return new WildcardTypeName(upperBounds, lowerBounds, concatAnnotations(annotations));
}
@Override
public TypeName withoutAnnotations() {
return new WildcardTypeName(upperBounds, lowerBounds);
}
@Override
CodeWriter emit(CodeWriter out) throws IOException {
if (lowerBounds.size() == 1) {
return out.emit("? super $T", lowerBounds.get(0));
}
return upperBounds.get(0).equals(TypeName.OBJECT)
? out.emit("?")
: out.emit("? extends $T", upperBounds.get(0));
}
/**
* Returns a type that represents an unknown type that extends {@code bound}. For example, if
* {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
* {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code
* ? extends Object}.
*/
public static WildcardTypeName subtypeOf(TypeName upperBound) {
return new WildcardTypeName(Collections.singletonList(upperBound), Collections.emptyList());
}
public static WildcardTypeName subtypeOf(Type upperBound) {
return subtypeOf(TypeName.get(upperBound));
}
/**
* Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
* bound} is {@code String.class}, this returns {@code ? super String}.
*/
public static WildcardTypeName supertypeOf(TypeName lowerBound) {
return new WildcardTypeName(Collections.singletonList(OBJECT),
Collections.singletonList(lowerBound));
}
public static WildcardTypeName supertypeOf(Type lowerBound) {
return supertypeOf(TypeName.get(lowerBound));
}
public static TypeName get(javax.lang.model.type.WildcardType mirror) {
return get(mirror, new LinkedHashMap<>());
}
static TypeName get(
javax.lang.model.type.WildcardType mirror,
Map<TypeParameterElement, TypeVariableName> typeVariables) {
TypeMirror extendsBound = mirror.getExtendsBound();
if (extendsBound == null) {
TypeMirror superBound = mirror.getSuperBound();
if (superBound == null) {
return subtypeOf(Object.class);
} else {
return supertypeOf(TypeName.get(superBound, typeVariables));
}
} else {
return subtypeOf(TypeName.get(extendsBound, typeVariables));
}
}
public static TypeName get(WildcardType wildcardName) {
return get(wildcardName, new LinkedHashMap<>());
}
static TypeName get(WildcardType wildcardName, Map<Type, TypeVariableName> map) {
return new WildcardTypeName(
list(wildcardName.getUpperBounds(), map),
list(wildcardName.getLowerBounds(), map));
}
}

View file

@ -33,5 +33,6 @@ dependencyResolutionManagement {
} }
} }
include ':javapoet'
include ':j2html-codegen' include ':j2html-codegen'
include ':j2html' include ':j2html'