aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZac Sweers <zac.sweers@gmail.com>2020-01-06 14:01:52 -0500
committerEgor Andreevich <egor@squareup.com>2020-01-06 14:01:51 -0500
commit80ddc99409399bd15b06509a6e3e75cb4117b8d2 (patch)
treea75ddcdf5e8be154aa08538ba06ca0af8d3dfc44
parent25d19845b866c04a2fb3e11ce3d43ccb2e7b98cd (diff)
downloadjavapoet-80ddc99409399bd15b06509a6e3e75cb4117b8d2.tar.gz
Add alwaysQualify() API to avoid collisions with known colliding types (#734)
* Add alwaysQualify() API to avoid collisions with known colliding types Implementation based on https://github.com/square/javapoet/issues/77#issuecomment-507387399 Resolves #77 CC @eamonnmcmanus * Add utility avoidClashesWithNestedClasses methods for Class/TypeElement * Fix style issues * Move scope to TypeSpecs * Check superclasses and superinterfaces * Add superclass and superinterface overloads * Style fixes * Add qualified names to toBuilder test * Add Map.Entry test + doc regression tests
-rw-r--r--src/main/java/com/squareup/javapoet/CodeWriter.java18
-rw-r--r--src/main/java/com/squareup/javapoet/JavaFile.java30
-rw-r--r--src/main/java/com/squareup/javapoet/TypeSpec.java173
-rw-r--r--src/test/java/com/squareup/javapoet/JavaFileTest.java301
-rw-r--r--src/test/java/com/squareup/javapoet/TypeSpecTest.java3
5 files changed, 515 insertions, 10 deletions
diff --git a/src/main/java/com/squareup/javapoet/CodeWriter.java b/src/main/java/com/squareup/javapoet/CodeWriter.java
index 2c634b5..3b2f188 100644
--- a/src/main/java/com/squareup/javapoet/CodeWriter.java
+++ b/src/main/java/com/squareup/javapoet/CodeWriter.java
@@ -54,6 +54,7 @@ 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<>();
@@ -68,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('.')));
@@ -409,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();
diff --git a/src/main/java/com/squareup/javapoet/JavaFile.java b/src/main/java/com/squareup/javapoet/JavaFile.java
index d5747a8..a419801 100644
--- a/src/main/java/com/squareup/javapoet/JavaFile.java
+++ b/src/main/java/com/squareup/javapoet/JavaFile.java
@@ -27,6 +27,7 @@ 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;
@@ -60,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) {
@@ -69,16 +71,33 @@ 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);
}
@@ -153,7 +172,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++;
}
diff --git a/src/main/java/com/squareup/javapoet/TypeSpec.java b/src/main/java/com/squareup/javapoet/TypeSpec.java
index 2395213..5fb2bb3 100644
--- a/src/main/java/com/squareup/javapoet/TypeSpec.java
+++ b/src/main/java/com/squareup/javapoet/TypeSpec.java
@@ -16,6 +16,7 @@
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;
@@ -24,6 +25,7 @@ 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;
@@ -31,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;
@@ -56,6 +63,7 @@ public final class TypeSpec {
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;
@@ -73,6 +81,7 @@ 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<>();
@@ -108,6 +117,7 @@ public final class TypeSpec {
this.typeSpecs = Collections.emptyList();
this.originatingElements = Collections.emptyList();
this.nestedTypesSimpleNames = Collections.emptySet();
+ this.alwaysQualifiedNames = Collections.emptySet();
}
public boolean hasModifier(Modifier modifier) {
@@ -169,6 +179,7 @@ public final class TypeSpec {
builder.initializerBlock.add(initializerBlock);
builder.staticBlock.add(staticBlock);
builder.originatingElements.addAll(originatingElements);
+ builder.alwaysQualifiedNames.addAll(alwaysQualifiedNames);
return builder;
}
@@ -414,6 +425,7 @@ public final class TypeSpec {
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) {
@@ -483,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) {
@@ -501,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) {
@@ -582,6 +655,102 @@ 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");
diff --git a/src/test/java/com/squareup/javapoet/JavaFileTest.java b/src/test/java/com/squareup/javapoet/JavaFileTest.java
index f7583f1..e75a019 100644
--- a/src/test/java/com/squareup/javapoet/JavaFileTest.java
+++ b/src/test/java/com/squareup/javapoet/JavaFileTest.java
@@ -16,12 +16,18 @@
package com.squareup.javapoet;
import java.io.File;
+import com.google.testing.compile.CompilationRule;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -30,6 +36,13 @@ import static com.google.common.truth.Truth.assertThat;
@RunWith(JUnit4.class)
public final class JavaFileTest {
+
+ @Rule public final CompilationRule compilation = new CompilationRule();
+
+ private TypeElement getElement(Class<?> clazz) {
+ return compilation.getElements().getTypeElement(clazz.getCanonicalName());
+ }
+
@Test public void importStaticReadmeExample() {
ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard");
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");
@@ -710,4 +723,292 @@ public final class JavaFileTest {
+ "class Taco {\n"
+ "}\n");
}
+
+ @Test public void alwaysQualifySimple() {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(Thread.class, "thread")
+ .alwaysQualify("Thread")
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " java.lang.Thread thread;\n"
+ + "}\n");
+ }
+
+ @Test public void alwaysQualifySupersedesJavaLangImports() {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ .addField(Thread.class, "thread")
+ .alwaysQualify("Thread")
+ .build())
+ .skipJavaLangImports(true)
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " java.lang.Thread thread;\n"
+ + "}\n");
+ }
+
+ @Test public void avoidClashesWithNestedClasses_viaClass() {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ // These two should get qualified
+ .addField(ClassName.get("other", "NestedTypeA"), "nestedA")
+ .addField(ClassName.get("other", "NestedTypeB"), "nestedB")
+ // This one shouldn't since it's not a nested type of Foo
+ .addField(ClassName.get("other", "NestedTypeC"), "nestedC")
+ // This one shouldn't since we only look at nested types
+ .addField(ClassName.get("other", "Foo"), "foo")
+ .avoidClashesWithNestedClasses(Foo.class)
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import other.Foo;\n"
+ + "import other.NestedTypeC;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " other.NestedTypeA nestedA;\n"
+ + "\n"
+ + " other.NestedTypeB nestedB;\n"
+ + "\n"
+ + " NestedTypeC nestedC;\n"
+ + "\n"
+ + " Foo foo;\n"
+ + "}\n");
+ }
+
+ @Test public void avoidClashesWithNestedClasses_viaTypeElement() {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ // These two should get qualified
+ .addField(ClassName.get("other", "NestedTypeA"), "nestedA")
+ .addField(ClassName.get("other", "NestedTypeB"), "nestedB")
+ // This one shouldn't since it's not a nested type of Foo
+ .addField(ClassName.get("other", "NestedTypeC"), "nestedC")
+ // This one shouldn't since we only look at nested types
+ .addField(ClassName.get("other", "Foo"), "foo")
+ .avoidClashesWithNestedClasses(getElement(Foo.class))
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo(""
+ + "package com.squareup.tacos;\n"
+ + "\n"
+ + "import other.Foo;\n"
+ + "import other.NestedTypeC;\n"
+ + "\n"
+ + "class Taco {\n"
+ + " other.NestedTypeA nestedA;\n"
+ + "\n"
+ + " other.NestedTypeB nestedB;\n"
+ + "\n"
+ + " NestedTypeC nestedC;\n"
+ + "\n"
+ + " Foo foo;\n"
+ + "}\n");
+ }
+
+ @Test public void avoidClashesWithNestedClasses_viaSuperinterfaceType() {
+ String source = JavaFile.builder("com.squareup.tacos",
+ TypeSpec.classBuilder("Taco")
+ // These two should get qualified
+ .addField(ClassName.get("other", "NestedTypeA"), "nestedA")
+ .addField(ClassName.get("other", "NestedTypeB"), "nestedB")
+ // This one shouldn't since it's not a nested type of Foo
+ .addField(ClassName.get("other", "NestedTypeC"), "nestedC")
+ // This one shouldn't since we only look at nested types
+ .addField(ClassName.get("other", "Foo"), "foo")
+ .addType(TypeSpec.classBuilder("NestedTypeA").build())
+ .addType(TypeSpec.classBuilder("NestedTypeB").build())
+ .addSuperinterface(FooInterface.class)
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo("package com.squareup.tacos;\n"
+ + "\n"
+ + "import com.squareup.javapoet.JavaFileTest;\n"
+ + "import other.Foo;\n"
+ + "import other.NestedTypeC;\n"
+ + "\n"
+ + "class Taco implements JavaFileTest.FooInterface {\n"
+ + " other.NestedTypeA nestedA;\n"
+ + "\n"
+ + " other.NestedTypeB nestedB;\n"
+ + "\n"
+ + " NestedTypeC nestedC;\n"
+ + "\n"
+ + " Foo foo;\n"
+ + "\n"
+ + " class NestedTypeA {\n"
+ + " }\n"
+ + "\n"
+ + " class NestedTypeB {\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ static class Foo {
+ static class NestedTypeA {
+
+ }
+ static class NestedTypeB {
+
+ }
+ }
+
+ interface FooInterface {
+ class NestedTypeA {
+
+ }
+ class NestedTypeB {
+
+ }
+ }
+
+ private TypeSpec.Builder childTypeBuilder() {
+ return TypeSpec.classBuilder("Child")
+ .addMethod(MethodSpec.methodBuilder("optionalString")
+ .returns(ParameterizedTypeName.get(Optional.class, String.class))
+ .addStatement("return $T.empty()", Optional.class)
+ .build())
+ .addMethod(MethodSpec.methodBuilder("pattern")
+ .returns(Pattern.class)
+ .addStatement("return null")
+ .build());
+ }
+
+ @Test
+ public void avoidClashes_parentChild_superclass_type() {
+ String source = JavaFile.builder("com.squareup.javapoet",
+ childTypeBuilder().superclass(Parent.class).build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Child extends JavaFileTest.Parent {\n"
+ + " java.util.Optional<String> optionalString() {\n"
+ + " return java.util.Optional.empty();\n"
+ + " }\n"
+ + "\n"
+ + " java.util.regex.Pattern pattern() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void avoidClashes_parentChild_superclass_typeMirror() {
+ String source = JavaFile.builder("com.squareup.javapoet",
+ childTypeBuilder().superclass(getElement(Parent.class).asType()).build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "\n"
+ + "class Child extends JavaFileTest.Parent {\n"
+ + " java.util.Optional<String> optionalString() {\n"
+ + " return java.util.Optional.empty();\n"
+ + " }\n"
+ + "\n"
+ + " java.util.regex.Pattern pattern() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void avoidClashes_parentChild_superinterface_type() {
+ String source = JavaFile.builder("com.squareup.javapoet",
+ childTypeBuilder().addSuperinterface(ParentInterface.class).build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "import java.util.regex.Pattern;\n"
+ + "\n"
+ + "class Child implements JavaFileTest.ParentInterface {\n"
+ + " java.util.Optional<String> optionalString() {\n"
+ + " return java.util.Optional.empty();\n"
+ + " }\n"
+ + "\n"
+ + " Pattern pattern() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ @Test
+ public void avoidClashes_parentChild_superinterface_typeMirror() {
+ String source = JavaFile.builder("com.squareup.javapoet",
+ childTypeBuilder().addSuperinterface(getElement(ParentInterface.class).asType()).build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+ + "\n"
+ + "import java.lang.String;\n"
+ + "import java.util.regex.Pattern;\n"
+ + "\n"
+ + "class Child implements JavaFileTest.ParentInterface {\n"
+ + " java.util.Optional<String> optionalString() {\n"
+ + " return java.util.Optional.empty();\n"
+ + " }\n"
+ + "\n"
+ + " Pattern pattern() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "}\n");
+ }
+
+ // Regression test for https://github.com/square/javapoet/issues/77
+ // This covers class and inheritance
+ static class Parent implements ParentInterface {
+ static class Pattern {
+
+ }
+ }
+
+ interface ParentInterface {
+ class Optional {
+
+ }
+ }
+
+ // Regression test for case raised here: https://github.com/square/javapoet/issues/77#issuecomment-519972404
+ @Test
+ public void avoidClashes_mapEntry() {
+ String source = JavaFile.builder("com.squareup.javapoet",
+ TypeSpec.classBuilder("MapType")
+ .addMethod(MethodSpec.methodBuilder("optionalString")
+ .returns(ClassName.get("com.foo", "Entry"))
+ .addStatement("return null")
+ .build())
+ .addSuperinterface(Map.class)
+ .build())
+ .build()
+ .toString();
+ assertThat(source).isEqualTo("package com.squareup.javapoet;\n"
+ + "\n"
+ + "import java.util.Map;\n"
+ + "\n"
+ + "class MapType implements Map {\n"
+ + " com.foo.Entry optionalString() {\n"
+ + " return null;\n"
+ + " }\n"
+ + "}\n");
+ }
}
diff --git a/src/test/java/com/squareup/javapoet/TypeSpecTest.java b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
index 4943fc9..0f67c5c 100644
--- a/src/test/java/com/squareup/javapoet/TypeSpecTest.java
+++ b/src/test/java/com/squareup/javapoet/TypeSpecTest.java
@@ -2284,12 +2284,15 @@ public final class TypeSpecTest {
.addStatement("foo = $S", "FOO")
.build())
.addOriginatingElement(originatingElement)
+ .alwaysQualify("com.example.AlwaysQualified")
.build();
TypeSpec recreatedTaco = taco.toBuilder().build();
assertThat(toString(taco)).isEqualTo(toString(recreatedTaco));
assertThat(taco.originatingElements)
.containsExactlyElementsIn(recreatedTaco.originatingElements);
+ assertThat(taco.alwaysQualifiedNames)
+ .containsExactlyElementsIn(recreatedTaco.alwaysQualifiedNames);
TypeSpec initializersAdded = taco.toBuilder()
.addInitializerBlock(CodeBlock.builder()