aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/squareup/javapoet/TypeSpec.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/squareup/javapoet/TypeSpec.java')
-rw-r--r--src/main/java/com/squareup/javapoet/TypeSpec.java307
1 files changed, 256 insertions, 51 deletions
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),