aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/squareup/javapoet/AnnotationSpec.java9
-rw-r--r--src/main/java/com/squareup/javapoet/ClassName.java35
-rw-r--r--src/main/java/com/squareup/javapoet/CodeBlock.java8
-rw-r--r--src/main/java/com/squareup/javapoet/CodeWriter.java72
-rw-r--r--src/main/java/com/squareup/javapoet/FieldSpec.java5
-rw-r--r--src/main/java/com/squareup/javapoet/JavaFile.java72
-rw-r--r--src/main/java/com/squareup/javapoet/LineWrapper.java38
-rw-r--r--src/main/java/com/squareup/javapoet/MethodSpec.java71
-rw-r--r--src/main/java/com/squareup/javapoet/ParameterSpec.java31
-rw-r--r--src/main/java/com/squareup/javapoet/TypeName.java2
-rw-r--r--src/main/java/com/squareup/javapoet/TypeSpec.java307
11 files changed, 556 insertions, 94 deletions
diff --git a/src/main/java/com/squareup/javapoet/AnnotationSpec.java b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
index d1c5e53..5525d7b 100644
--- a/src/main/java/com/squareup/javapoet/AnnotationSpec.java
+++ b/src/main/java/com/squareup/javapoet/AnnotationSpec.java
@@ -192,7 +192,8 @@ public final class AnnotationSpec {
public static final class Builder {
private final TypeName type;
- private final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
+
+ public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>();
private Builder(TypeName type) {
this.type = type;
@@ -203,8 +204,6 @@ public final class AnnotationSpec {
}
public Builder addMember(String name, CodeBlock codeBlock) {
- checkNotNull(name, "name == null");
- checkArgument(SourceVersion.isName(name), "not a valid name: %s", name);
List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>());
values.add(codeBlock);
return this;
@@ -238,6 +237,10 @@ public final class AnnotationSpec {
}
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);
}
}
diff --git a/src/main/java/com/squareup/javapoet/ClassName.java b/src/main/java/com/squareup/javapoet/ClassName.java
index 99c4ed2..4bef49d 100644
--- a/src/main/java/com/squareup/javapoet/ClassName.java
+++ b/src/main/java/com/squareup/javapoet/ClassName.java
@@ -20,6 +20,7 @@ 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;
@@ -32,6 +33,9 @@ import static com.squareup.javapoet.Util.checkNotNull;
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;
@@ -41,6 +45,8 @@ public final class ClassName extends TypeName implements Comparable<ClassName> {
/** 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;
@@ -51,7 +57,7 @@ public final class ClassName extends TypeName implements Comparable<ClassName> {
private ClassName(String packageName, ClassName enclosingClassName, String simpleName,
List<AnnotationSpec> annotations) {
super(annotations);
- this.packageName = packageName;
+ this.packageName = Objects.requireNonNull(packageName, "packageName == null");
this.enclosingClassName = enclosingClassName;
this.simpleName = simpleName;
this.canonicalName = enclosingClassName != null
@@ -108,11 +114,18 @@ public final class ClassName extends TypeName implements Comparable<ClassName> {
}
public List<String> simpleNames() {
- List<String> simpleNames = new ArrayList<>();
- if (enclosingClassName != null) {
- simpleNames.addAll(enclosingClassName().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);
}
- simpleNames.add(simpleName);
return simpleNames;
}
@@ -138,6 +151,14 @@ public final class ClassName extends TypeName implements Comparable<ClassName> {
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");
@@ -155,7 +176,7 @@ public final class ClassName extends TypeName implements Comparable<ClassName> {
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) : null;
+ String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : NO_PACKAGE;
return new ClassName(packageName, null, name);
}
@@ -177,7 +198,7 @@ public final class ClassName extends TypeName implements Comparable<ClassName> {
p = classNameString.indexOf('.', p) + 1;
checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
}
- String packageName = p == 0 ? "" : classNameString.substring(0, p - 1);
+ String packageName = p == 0 ? NO_PACKAGE : classNameString.substring(0, p - 1);
// Add class names like "Map" and "Entry".
ClassName className = null;
diff --git a/src/main/java/com/squareup/javapoet/CodeBlock.java b/src/main/java/com/squareup/javapoet/CodeBlock.java
index 33e3846..02542f5 100644
--- a/src/main/java/com/squareup/javapoet/CodeBlock.java
+++ b/src/main/java/com/squareup/javapoet/CodeBlock.java
@@ -189,7 +189,7 @@ public final class CodeBlock {
while (p < format.length()) {
int nextP = format.indexOf("$", p);
if (nextP == -1) {
- formatParts.add(format.substring(p, format.length()));
+ formatParts.add(format.substring(p));
break;
}
@@ -424,6 +424,12 @@ public final class CodeBlock {
return this;
}
+ public Builder clear() {
+ formatParts.clear();
+ args.clear();
+ return this;
+ }
+
public CodeBlock build() {
return new CodeBlock(this);
}
diff --git a/src/main/java/com/squareup/javapoet/CodeWriter.java b/src/main/java/com/squareup/javapoet/CodeWriter.java
index 542f434..3b2f188 100644
--- a/src/main/java/com/squareup/javapoet/CodeWriter.java
+++ b/src/main/java/com/squareup/javapoet/CodeWriter.java
@@ -54,9 +54,11 @@ final class CodeWriter {
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;
/**
@@ -67,19 +69,23 @@ final class CodeWriter {
int statementLine = -1;
CodeWriter(Appendable out) {
- this(out, " ", Collections.emptySet());
+ this(out, " ", Collections.emptySet(), Collections.emptySet());
}
- CodeWriter(Appendable out, String indent, Set<String> staticImports) {
- this(out, indent, Collections.emptyMap(), staticImports);
+ 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) {
+ 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('.')));
@@ -148,7 +154,7 @@ final class CodeWriter {
emit("/**\n");
javadoc = true;
try {
- emit(javadocCodeBlock);
+ emit(javadocCodeBlock, true);
} finally {
javadoc = false;
}
@@ -187,6 +193,8 @@ final class CodeWriter {
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) {
@@ -203,6 +211,10 @@ final class CodeWriter {
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);
}
@@ -212,6 +224,10 @@ final class CodeWriter {
}
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();
@@ -300,6 +316,9 @@ final class CodeWriter {
break;
}
}
+ if (ensureTrailingNewline && out.lastChar() != '\n') {
+ emit("\n");
+ }
return this;
}
@@ -353,6 +372,12 @@ final class CodeWriter {
* 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;
@@ -374,7 +399,7 @@ final class CodeWriter {
// If the class is in the same package, we're done.
if (Objects.equals(packageName, className.packageName())) {
- referencedNames.add(className.topLevelClassName().simpleName());
+ referencedNames.add(topLevelSimpleName);
return join(".", className.simpleNames());
}
@@ -389,6 +414,9 @@ final class CodeWriter {
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();
@@ -407,10 +435,8 @@ final class CodeWriter {
// Match a child of the current (potentially nested) class.
for (int i = typeSpecStack.size() - 1; i >= 0; i--) {
TypeSpec typeSpec = typeSpecStack.get(i);
- for (TypeSpec visibleChild : typeSpec.typeSpecs) {
- if (Objects.equals(visibleChild.name, simpleName)) {
- return stackClassName(i, simpleName);
- }
+ if (typeSpec.nestedTypesSimpleNames.contains(simpleName)) {
+ return stackClassName(i, simpleName);
}
}
@@ -443,7 +469,7 @@ final class CodeWriter {
*/
CodeWriter emitAndIndent(String s) throws IOException {
boolean first = true;
- for (String line : s.split("\n", -1)) {
+ for (String line : s.split("\\R", -1)) {
// Emit a newline character. Make sure blank lines in Javadoc & comments look good.
if (!first) {
if ((javadoc || comment) && trailingNewline) {
@@ -494,4 +520,26 @@ final class CodeWriter {
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;
+ }
+ }
}
diff --git a/src/main/java/com/squareup/javapoet/FieldSpec.java b/src/main/java/com/squareup/javapoet/FieldSpec.java
index 851b36d..f530d6e 100644
--- a/src/main/java/com/squareup/javapoet/FieldSpec.java
+++ b/src/main/java/com/squareup/javapoet/FieldSpec.java
@@ -111,10 +111,11 @@ public final class FieldSpec {
private final String name;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
- private final List<AnnotationSpec> annotations = new ArrayList<>();
- private final List<Modifier> modifiers = new ArrayList<>();
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;
diff --git a/src/main/java/com/squareup/javapoet/JavaFile.java b/src/main/java/com/squareup/javapoet/JavaFile.java
index e7662dd..da3dd86 100644
--- a/src/main/java/com/squareup/javapoet/JavaFile.java
+++ b/src/main/java/com/squareup/javapoet/JavaFile.java
@@ -22,10 +22,12 @@ 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;
@@ -59,6 +61,7 @@ public final class JavaFile {
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) {
@@ -68,21 +71,63 @@ public final class JavaFile {
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);
+ 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);
+ 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;
@@ -94,9 +139,11 @@ public final class JavaFile {
}
Path outputPath = outputDirectory.resolve(typeSpec.name + ".java");
- try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8)) {
+ 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. */
@@ -104,6 +151,15 @@ public final class JavaFile {
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()
@@ -144,7 +200,12 @@ public final class JavaFile {
int importedTypesCount = 0;
for (ClassName className : new TreeSet<>(codeWriter.importedTypes().values())) {
- if (skipJavaLangImports && className.packageName().equals("java.lang")) continue;
+ // 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++;
}
@@ -216,10 +277,11 @@ public final class JavaFile {
private final String packageName;
private final TypeSpec typeSpec;
private final CodeBlock.Builder fileComment = CodeBlock.builder();
- private final Set<String> staticImports = new TreeSet<>();
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;
diff --git a/src/main/java/com/squareup/javapoet/LineWrapper.java b/src/main/java/com/squareup/javapoet/LineWrapper.java
index 6aa3131..928d9f4 100644
--- a/src/main/java/com/squareup/javapoet/LineWrapper.java
+++ b/src/main/java/com/squareup/javapoet/LineWrapper.java
@@ -24,7 +24,7 @@ import static com.squareup.javapoet.Util.checkNotNull;
* or soft-wrapping spaces using {@link #wrappingSpace}.
*/
final class LineWrapper {
- private final Appendable out;
+ private final RecordingAppendable out;
private final String indent;
private final int columnLimit;
private boolean closed;
@@ -47,11 +47,16 @@ final class LineWrapper {
LineWrapper(Appendable out, String indent, int columnLimit) {
checkNotNull(out, "out == null");
- this.out = out;
+ 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");
@@ -134,4 +139,33 @@ final class LineWrapper {
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);
+ }
+ }
}
diff --git a/src/main/java/com/squareup/javapoet/MethodSpec.java b/src/main/java/com/squareup/javapoet/MethodSpec.java
index a2c7c43..2284ef5 100644
--- a/src/main/java/com/squareup/javapoet/MethodSpec.java
+++ b/src/main/java/com/squareup/javapoet/MethodSpec.java
@@ -24,6 +24,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
@@ -82,7 +83,7 @@ public final class MethodSpec {
void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers)
throws IOException {
- codeWriter.emitJavadoc(javadoc);
+ codeWriter.emitJavadoc(javadocWithParameters());
codeWriter.emitAnnotations(annotations, false);
codeWriter.emitModifiers(modifiers, implicitModifiers);
@@ -132,11 +133,26 @@ public final class MethodSpec {
codeWriter.emit(" {\n");
codeWriter.indent();
- codeWriter.emit(code);
+ 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) {
@@ -217,7 +233,16 @@ public final class MethodSpec {
}
methodBuilder.returns(TypeName.get(method.getReturnType()));
- methodBuilder.addParameters(ParameterSpec.parametersOf(method));
+ // Copying parameter annotations from the overridden method can be incorrect so we're
+ // deliberately dropping them. See https://github.com/square/javapoet/issues/482.
+ methodBuilder.addParameters(ParameterSpec.parametersOf(method)
+ .stream()
+ .map(parameterSpec -> {
+ ParameterSpec.Builder builder = parameterSpec.toBuilder();
+ builder.annotations.clear();
+ return builder.build();
+ })
+ .collect(Collectors.toList()));
methodBuilder.varargs(method.isVarArgs());
for (TypeMirror thrownType : method.getThrownTypes()) {
@@ -277,25 +302,31 @@ public final class MethodSpec {
}
public static final class Builder {
- private final String name;
+ private String name;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
- private final List<AnnotationSpec> annotations = new ArrayList<>();
- private final List<Modifier> modifiers = new ArrayList<>();
- private List<TypeVariableName> typeVariables = new ArrayList<>();
private TypeName returnType;
- private final List<ParameterSpec> parameters = new ArrayList<>();
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) {
@@ -454,6 +485,14 @@ public final class MethodSpec {
}
/**
+ * @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.
*/
@@ -462,6 +501,14 @@ public final class MethodSpec {
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;
@@ -476,6 +523,14 @@ public final class MethodSpec {
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;
diff --git a/src/main/java/com/squareup/javapoet/ParameterSpec.java b/src/main/java/com/squareup/javapoet/ParameterSpec.java
index 63da3f2..b8f3129 100644
--- a/src/main/java/com/squareup/javapoet/ParameterSpec.java
+++ b/src/main/java/com/squareup/javapoet/ParameterSpec.java
@@ -21,7 +21,9 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
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;
@@ -35,12 +37,14 @@ public final class ParameterSpec {
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) {
@@ -81,10 +85,19 @@ public final class ParameterSpec {
}
public static ParameterSpec get(VariableElement element) {
+ checkArgument(element.getKind().equals(ElementKind.PARAMETER), "element is not a parameter");
+
+ // Copy over any annotations from element.
+ List<AnnotationSpec> annotations = element.getAnnotationMirrors()
+ .stream()
+ .map((mirror) -> AnnotationSpec.get(mirror))
+ .collect(Collectors.toList());
+
TypeName type = TypeName.get(element.asType());
String name = element.getSimpleName().toString();
return ParameterSpec.builder(type, name)
.addModifiers(element.getModifiers())
+ .addAnnotations(annotations)
.build();
}
@@ -121,15 +134,26 @@ public final class ParameterSpec {
public static final class Builder {
private final TypeName type;
private final String name;
+ private final CodeBlock.Builder javadoc = CodeBlock.builder();
- private final List<AnnotationSpec> annotations = new ArrayList<>();
- private final List<Modifier> modifiers = new ArrayList<>();
+ 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) {
@@ -160,6 +184,9 @@ public final class ParameterSpec {
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;
diff --git a/src/main/java/com/squareup/javapoet/TypeName.java b/src/main/java/com/squareup/javapoet/TypeName.java
index 38877f7..c0986bb 100644
--- a/src/main/java/com/squareup/javapoet/TypeName.java
+++ b/src/main/java/com/squareup/javapoet/TypeName.java
@@ -44,7 +44,7 @@ import javax.lang.model.util.SimpleTypeVisitor8;
* 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.lang.List} doesn't know about the {@code size()} method, the fact that
+ * 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
diff --git a/src/main/java/com/squareup/javapoet/TypeSpec.java b/src/main/java/com/squareup/javapoet/TypeSpec.java
index 46de3a5..5fb2bb3 100644
--- a/src/main/java/com/squareup/javapoet/TypeSpec.java
+++ b/src/main/java/com/squareup/javapoet/TypeSpec.java
@@ -16,13 +16,16 @@
package com.squareup.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;
@@ -30,6 +33,11 @@ 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 com.squareup.javapoet.Util.checkArgument;
import static com.squareup.javapoet.Util.checkNotNull;
@@ -53,7 +61,9 @@ public final class TypeSpec {
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;
@@ -71,12 +81,16 @@ public final class TypeSpec {
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);
}
@@ -102,6 +116,8 @@ public final class TypeSpec {
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) {
@@ -133,9 +149,7 @@ public final class TypeSpec {
}
public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) {
- return anonymousClassBuilder(CodeBlock.builder()
- .add(typeArgumentsFormat, args)
- .build());
+ return anonymousClassBuilder(CodeBlock.of(typeArgumentsFormat, args));
}
public static Builder anonymousClassBuilder(CodeBlock typeArguments) {
@@ -164,6 +178,8 @@ public final class TypeSpec {
builder.typeSpecs.addAll(typeSpecs);
builder.initializerBlock.add(initializerBlock);
builder.staticBlock.add(staticBlock);
+ builder.originatingElements.addAll(originatingElements);
+ builder.alwaysQualifiedNames.addAll(alwaysQualifiedNames);
return builder;
}
@@ -316,6 +332,7 @@ public final class TypeSpec {
codeWriter.unindent();
codeWriter.popType();
+ codeWriter.popTypeVariables(typeVariables);
codeWriter.emit("}");
if (enumName == null && anonymousTypeArguments == null) {
@@ -395,18 +412,20 @@ public final class TypeSpec {
private final CodeBlock anonymousTypeArguments;
private final CodeBlock.Builder javadoc = CodeBlock.builder();
- private final List<AnnotationSpec> annotations = new ArrayList<>();
- private final List<Modifier> modifiers = new ArrayList<>();
- private final List<TypeVariableName> typeVariables = new ArrayList<>();
private TypeName superclass = ClassName.OBJECT;
- private final List<TypeName> superinterfaces = new ArrayList<>();
- private final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>();
- private final List<FieldSpec> fieldSpecs = new ArrayList<>();
private final CodeBlock.Builder staticBlock = CodeBlock.builder();
private final CodeBlock.Builder initializerBlock = CodeBlock.builder();
- private final List<MethodSpec> methodSpecs = new ArrayList<>();
- private final List<TypeSpec> typeSpecs = new ArrayList<>();
- private final List<Element> originatingElements = new ArrayList<>();
+
+ 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) {
@@ -449,16 +468,11 @@ public final class TypeSpec {
}
public Builder addModifiers(Modifier... modifiers) {
- checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
- for (Modifier modifier : modifiers) {
- checkArgument(modifier != null, "modifiers contain null");
- this.modifiers.add(modifier);
- }
+ Collections.addAll(this.modifiers, modifiers);
return this;
}
public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) {
- checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
checkArgument(typeVariables != null, "typeVariables == null");
for (TypeVariableName typeVariable : typeVariables) {
this.typeVariables.add(typeVariable);
@@ -467,7 +481,6 @@ public final class TypeSpec {
}
public Builder addTypeVariable(TypeVariableName typeVariable) {
- checkState(anonymousTypeArguments == null, "forbidden on anonymous types.");
typeVariables.add(typeVariable);
return this;
}
@@ -482,7 +495,32 @@ public final class TypeSpec {
}
public Builder superclass(Type superclass) {
- return superclass(TypeName.get(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) {
@@ -500,7 +538,43 @@ public final class TypeSpec {
}
public Builder addSuperinterface(Type superinterface) {
- return addSuperinterface(TypeName.get(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) {
@@ -508,10 +582,6 @@ public final class TypeSpec {
}
public Builder addEnumConstant(String name, TypeSpec typeSpec) {
- checkState(kind == Kind.ENUM, "%s is not enum", this.name);
- checkArgument(typeSpec.anonymousTypeArguments != null,
- "enum constants must have anonymous type arguments");
- checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name);
enumConstants.put(name, typeSpec);
return this;
}
@@ -525,12 +595,6 @@ public final class TypeSpec {
}
public Builder addField(FieldSpec fieldSpec) {
- 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);
- }
fieldSpecs.add(fieldSpec);
return this;
}
@@ -569,23 +633,6 @@ public final class TypeSpec {
}
public Builder addMethod(MethodSpec methodSpec) {
- if (kind == Kind.INTERFACE) {
- requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC,
- Modifier.DEFAULT);
- requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
- } 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);
- }
methodSpecs.add(methodSpec);
return this;
}
@@ -599,9 +646,6 @@ public final class TypeSpec {
}
public Builder addType(TypeSpec typeSpec) {
- checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers),
- "%s %s.%s requires modifiers %s", kind, name, typeSpec.name,
- kind.implicitTypeModifiers);
typeSpecs.add(typeSpec);
return this;
}
@@ -611,10 +655,171 @@ public final class TypeSpec {
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");
+ }
+ }
+
checkArgument(kind != Kind.ENUM || !enumConstants.isEmpty(),
"at least one enum constant is required for %s", name);
+ 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.ABSTRACT, Modifier.STATIC,
+ Modifier.DEFAULT);
+ requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE);
+ } 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),