diff options
author | Zac Sweers <zac.sweers@gmail.com> | 2020-01-06 14:01:52 -0500 |
---|---|---|
committer | Egor Andreevich <egor@squareup.com> | 2020-01-06 14:01:51 -0500 |
commit | 80ddc99409399bd15b06509a6e3e75cb4117b8d2 (patch) | |
tree | a75ddcdf5e8be154aa08538ba06ca0af8d3dfc44 | |
parent | 25d19845b866c04a2fb3e11ce3d43ccb2e7b98cd (diff) | |
download | javapoet-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.java | 18 | ||||
-rw-r--r-- | src/main/java/com/squareup/javapoet/JavaFile.java | 30 | ||||
-rw-r--r-- | src/main/java/com/squareup/javapoet/TypeSpec.java | 173 | ||||
-rw-r--r-- | src/test/java/com/squareup/javapoet/JavaFileTest.java | 301 | ||||
-rw-r--r-- | src/test/java/com/squareup/javapoet/TypeSpecTest.java | 3 |
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() |