diff options
Diffstat (limited to 'src/main/java/com/squareup/javapoet/TypeSpec.java')
-rw-r--r-- | src/main/java/com/squareup/javapoet/TypeSpec.java | 307 |
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), |