clean up and reformat code, integrate javapoet, lift tests to java modules
This commit is contained in:
parent
f5d00d8913
commit
b2fda364b5
327 changed files with 9031 additions and 2068 deletions
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
7
j2html-codegen/src/main/java/module-info.java
Normal file
7
j2html-codegen/src/main/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -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;
|
||||||
|
|
12
j2html-codegen/src/test/java/module-info.java
Normal file
12
j2html-codegen/src/test/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -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);
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.j2html.codegen.wattsi;
|
package org.xbib.j2html.codegen.test.wattsi;
|
||||||
|
|
||||||
public interface AttributeDefinition {
|
public interface AttributeDefinition {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.j2html.codegen.wattsi;
|
package org.xbib.j2html.codegen.test.wattsi;
|
||||||
|
|
||||||
public interface ElementDefinition {
|
public interface ElementDefinition {
|
||||||
|
|
|
@ -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)
|
|
@ -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;
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
19
j2html/src/test/java/module-info.java
Normal file
19
j2html/src/test/java/module-info.java
Normal 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;
|
||||||
|
}
|
|
@ -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 = "<body>" + EOL + " Any content" + EOL + "</body>" + 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");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
|
@ -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)
|
||||||
))
|
))
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.j2html.model;
|
package org.xbib.j2html.test.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.j2html.model;
|
package org.xbib.j2html.test.model;
|
||||||
|
|
||||||
public class PageModel {
|
public class PageModel {
|
||||||
|
|
|
@ -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;
|
|
@ -1,4 +1,4 @@
|
||||||
package org.xbib.j2html.model;
|
package org.xbib.j2html.test.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -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")
|
|
@ -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")
|
||||||
);
|
);
|
|
@ -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;
|
|
@ -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.*;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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")));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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;
|
|
@ -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 = "<body>" + EOL + " Any content" + EOL + "</body>" + 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
4
j2html/src/test/resources/logging.properties
Normal file
4
j2html/src/test/resources/logging.properties
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
handlers=java.util.logging.ConsoleHandler
|
||||||
|
.level=ALL
|
||||||
|
java.util.logging.ConsoleHandler.level=ALL
|
||||||
|
jdk.event.security.level=INFO
|
4
javapoet/src/main/java/module-info.java
Normal file
4
javapoet/src/main/java/module-info.java
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
module org.xbib.javapoet {
|
||||||
|
requires java.compiler;
|
||||||
|
exports org.xbib.javapoet;
|
||||||
|
}
|
278
javapoet/src/main/java/org/xbib/javapoet/AnnotationSpec.java
Normal file
278
javapoet/src/main/java/org/xbib/javapoet/AnnotationSpec.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
104
javapoet/src/main/java/org/xbib/javapoet/ArrayTypeName.java
Normal file
104
javapoet/src/main/java/org/xbib/javapoet/ArrayTypeName.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
314
javapoet/src/main/java/org/xbib/javapoet/ClassName.java
Normal file
314
javapoet/src/main/java/org/xbib/javapoet/ClassName.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
459
javapoet/src/main/java/org/xbib/javapoet/CodeBlock.java
Normal file
459
javapoet/src/main/java/org/xbib/javapoet/CodeBlock.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
535
javapoet/src/main/java/org/xbib/javapoet/CodeWriter.java
Normal file
535
javapoet/src/main/java/org/xbib/javapoet/CodeWriter.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
164
javapoet/src/main/java/org/xbib/javapoet/FieldSpec.java
Normal file
164
javapoet/src/main/java/org/xbib/javapoet/FieldSpec.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
342
javapoet/src/main/java/org/xbib/javapoet/JavaFile.java
Normal file
342
javapoet/src/main/java/org/xbib/javapoet/JavaFile.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
176
javapoet/src/main/java/org/xbib/javapoet/LineWrapper.java
Normal file
176
javapoet/src/main/java/org/xbib/javapoet/LineWrapper.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
527
javapoet/src/main/java/org/xbib/javapoet/MethodSpec.java
Normal file
527
javapoet/src/main/java/org/xbib/javapoet/MethodSpec.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
154
javapoet/src/main/java/org/xbib/javapoet/NameAllocator.java
Normal file
154
javapoet/src/main/java/org/xbib/javapoet/NameAllocator.java
Normal 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
|
||||||
|
*
|
||||||
|
* @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));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
191
javapoet/src/main/java/org/xbib/javapoet/ParameterSpec.java
Normal file
191
javapoet/src/main/java/org/xbib/javapoet/ParameterSpec.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
404
javapoet/src/main/java/org/xbib/javapoet/TypeName.java
Normal file
404
javapoet/src/main/java/org/xbib/javapoet/TypeName.java
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
832
javapoet/src/main/java/org/xbib/javapoet/TypeSpec.java
Normal file
832
javapoet/src/main/java/org/xbib/javapoet/TypeSpec.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
168
javapoet/src/main/java/org/xbib/javapoet/TypeVariableName.java
Normal file
168
javapoet/src/main/java/org/xbib/javapoet/TypeVariableName.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
125
javapoet/src/main/java/org/xbib/javapoet/Util.java
Normal file
125
javapoet/src/main/java/org/xbib/javapoet/Util.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
116
javapoet/src/main/java/org/xbib/javapoet/WildcardTypeName.java
Normal file
116
javapoet/src/main/java/org/xbib/javapoet/WildcardTypeName.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,5 +33,6 @@ dependencyResolutionManagement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
include ':javapoet'
|
||||||
include ':j2html-codegen'
|
include ':j2html-codegen'
|
||||||
include ':j2html'
|
include ':j2html'
|
||||||
|
|
Loading…
Reference in a new issue