New upstream version 1.12.1
tony mancill
3 years ago
5 | 5 | # https://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ |
6 | 6 | |
7 | 7 | SLUG="square/javapoet" |
8 | JDK="oraclejdk8" | |
8 | JDK="openjdk8" | |
9 | 9 | BRANCH="master" |
10 | 10 | |
11 | 11 | set -e |
1 | 1 | |
2 | 2 | matrix: |
3 | 3 | include: |
4 | - env: JDK='Oracle JDK 8' | |
5 | jdk: oraclejdk8 | |
6 | - env: JDK='Oracle JDK 9' | |
7 | jdk: oraclejdk9 | |
8 | - env: JDK='Oracle JDK 10' | |
9 | install: . ./install-jdk.sh -F 10 -L BCL | |
10 | - env: JDK='OpenJDK 10' | |
11 | install: . ./install-jdk.sh -F 10 -L GPL | |
12 | - env: JDK='Oracle JDK 11' | |
13 | install: . ./install-jdk.sh -F 11 -L BCL | |
14 | - env: JDK='OpenJDK 11' | |
15 | install: . ./install-jdk.sh -F 11 -L GPL | |
4 | - jdk: openjdk8 | |
5 | - jdk: openjdk11 | |
16 | 6 | allow_failures: |
17 | # ErrorProne/javac is not yet working on JDK 11 | |
18 | - env: JDK='Oracle JDK 11' | |
19 | - env: JDK='OpenJDK 11' | |
7 | - jdk: openjdk11 | |
20 | 8 | |
21 | # Direct usage of `install-jdk.sh` might be superseded by https://github.com/travis-ci/travis-build/pull/1347 | |
22 | 9 | before_install: |
23 | 10 | - unset _JAVA_OPTIONS |
24 | - wget https://github.com/sormuras/bach/raw/1.0.1/install-jdk.sh | |
25 | 11 | |
26 | 12 | after_success: |
27 | 13 | - .buildscript/deploy_snapshot.sh |
38 | 24 | notifications: |
39 | 25 | email: false |
40 | 26 | |
41 | sudo: false | |
42 | ||
43 | 27 | cache: |
44 | 28 | directories: |
45 | 29 | - $HOME/.m2 |
0 | 0 | Change Log |
1 | 1 | ========== |
2 | ||
3 | JavaPoet 1.12.0 *(2020-01-09)* | |
4 | ----------------------------- | |
5 | ||
6 | * New: Add `JavaFile.writeToPath()` and `JavaFile.writeToFile()` methods that return paths to the | |
7 | generated file as `Path` and `File` respectively. | |
8 | * New: Add `TypeSpec.alwaysQualify()` API to avoid clashes involving nested type names. | |
9 | * New: Add overloads accepting `CodeBlock`s to `MethodSpec`'s control flow methods. | |
10 | * New: Make list fields of all `Builder` types mutable. | |
11 | * New: Add `CodeBlock.clear()`. | |
12 | * New: Allow passing a custom `Charset` to `JavaFile.writeTo()`. | |
13 | * New: Improved performance of `ClassName.simpleNames()` by memoizing results. | |
14 | * New: Significant performance improvements for `CodeWriter.resolve()` as all nested simple names | |
15 | of a `TypeSpec` get pre-computed. | |
16 | * New: Add `TypeName.Builder.setName()` to allow overriding names passed in the constructor. | |
17 | * New: Add `TypeName.canonicalName()`. | |
18 | * Fix: Use `\\R` instead of `\n` as line separator in `CodeWriter.emitAndIndent()`. | |
19 | * Fix: Copy originating elements in `TypeSpec.toBuilder()`. | |
20 | * Fix: Ensure trailing newlines in Javadocs and method bodies. | |
21 | * Fix: Copy annotations when creating a `ParameterSpec` from a `VariableElement`. | |
22 | * Fix: Properly handle classes located in empty packages in `ClassName`. | |
23 | * Fix: Only allow `final` modifier on a `ParameterSpec`. | |
24 | * Fix: Use fully-qualified names for type names that are masked by type variable names. | |
25 | ||
2 | 26 | |
3 | 27 | JavaPoet 1.11.1 *(2018-05-16)* |
4 | 28 | ----------------------------- |
127 | 127 | Methods generating methods! And since JavaPoet generates source instead of bytecode, you can |
128 | 128 | read through it to make sure it's right. |
129 | 129 | |
130 | Some control flow statements, such as `if/else`, can have unlimited control flow possibilities. | |
131 | You can handle those options using `nextControlFlow()`: | |
132 | ||
133 | ```java | |
134 | MethodSpec main = MethodSpec.methodBuilder("main") | |
135 | .addStatement("long now = $T.currentTimeMillis()", System.class) | |
136 | .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class) | |
137 | .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!") | |
138 | .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class) | |
139 | .addStatement("$T.out.println($S)", System.class, "Time stood still!") | |
140 | .nextControlFlow("else") | |
141 | .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward") | |
142 | .endControlFlow() | |
143 | .build(); | |
144 | ``` | |
145 | ||
146 | Which generates: | |
147 | ||
148 | ```java | |
149 | void main() { | |
150 | long now = System.currentTimeMillis(); | |
151 | if (System.currentTimeMillis() < now) { | |
152 | System.out.println("Time travelling, woo hoo!"); | |
153 | } else if (System.currentTimeMillis() == now) { | |
154 | System.out.println("Time stood still!"); | |
155 | } else { | |
156 | System.out.println("Ok, time still moving forward"); | |
157 | } | |
158 | } | |
159 | ``` | |
160 | ||
161 | Catching exceptions using `try/catch` is also a use case for `nextControlFlow()`: | |
162 | ||
163 | ```java | |
164 | MethodSpec main = MethodSpec.methodBuilder("main") | |
165 | .beginControlFlow("try") | |
166 | .addStatement("throw new Exception($S)", "Failed") | |
167 | .nextControlFlow("catch ($T e)", Exception.class) | |
168 | .addStatement("throw new $T(e)", RuntimeException.class) | |
169 | .endControlFlow() | |
170 | .build(); | |
171 | ``` | |
172 | ||
173 | Which produces: | |
174 | ||
175 | ```java | |
176 | void main() { | |
177 | try { | |
178 | throw new Exception("Failed"); | |
179 | } catch (Exception e) { | |
180 | throw new RuntimeException(e); | |
181 | } | |
182 | } | |
183 | ``` | |
130 | 184 | |
131 | 185 | ### $L for Literals |
132 | 186 | |
673 | 727 | |
674 | 728 | ### Anonymous Inner Classes |
675 | 729 | |
676 | In the enum code, we used `Types.anonymousInnerClass()`. Anonymous inner classes can also be used in | |
730 | In the enum code, we used `TypeSpec.anonymousInnerClass()`. Anonymous inner classes can also be used in | |
677 | 731 | code blocks. They are values that can be referenced with `$L`: |
678 | 732 | |
679 | 733 | ```java |
837 | 891 | <dependency> |
838 | 892 | <groupId>com.squareup</groupId> |
839 | 893 | <artifactId>javapoet</artifactId> |
840 | <version>1.11.1</version> | |
894 | <version>1.12.0</version> | |
841 | 895 | </dependency> |
842 | 896 | ``` |
843 | 897 | or Gradle: |
844 | 898 | ```groovy |
845 | compile 'com.squareup:javapoet:1.11.1' | |
899 | compile 'com.squareup:javapoet:1.12.0' | |
846 | 900 | ``` |
847 | 901 | |
848 | 902 | Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. |
10 | 10 | |
11 | 11 | <groupId>com.squareup</groupId> |
12 | 12 | <artifactId>javapoet</artifactId> |
13 | <version>1.11.1</version> | |
13 | <version>1.12.1</version> | |
14 | 14 | |
15 | 15 | <name>JavaPoet</name> |
16 | 16 | <description>Use beautiful Java code to generate beautiful Java code.</description> |
93 | 93 | <plugin> |
94 | 94 | <groupId>org.apache.maven.plugins</groupId> |
95 | 95 | <artifactId>maven-compiler-plugin</artifactId> |
96 | <version>3.7.0</version> | |
96 | <version>3.8.0</version> | |
97 | 97 | <configuration> |
98 | 98 | <compilerId>javac-with-errorprone</compilerId> |
99 | 99 | <forceJavacCompilerUse>true</forceJavacCompilerUse> |
122 | 122 | <dependency> |
123 | 123 | <groupId>com.puppycrawl.tools</groupId> |
124 | 124 | <artifactId>checkstyle</artifactId> |
125 | <version>8.7</version> | |
125 | <version>8.18</version> | |
126 | 126 | </dependency> |
127 | 127 | </dependencies> |
128 | 128 | <configuration> |
191 | 191 | |
192 | 192 | public static final class Builder { |
193 | 193 | private final TypeName type; |
194 | private final Map<String, List<CodeBlock>> members = new LinkedHashMap<>(); | |
194 | ||
195 | public final Map<String, List<CodeBlock>> members = new LinkedHashMap<>(); | |
195 | 196 | |
196 | 197 | private Builder(TypeName type) { |
197 | 198 | this.type = type; |
202 | 203 | } |
203 | 204 | |
204 | 205 | public Builder addMember(String name, CodeBlock codeBlock) { |
205 | checkNotNull(name, "name == null"); | |
206 | checkArgument(SourceVersion.isName(name), "not a valid name: %s", name); | |
207 | 206 | List<CodeBlock> values = members.computeIfAbsent(name, k -> new ArrayList<>()); |
208 | 207 | values.add(codeBlock); |
209 | 208 | return this; |
237 | 236 | } |
238 | 237 | |
239 | 238 | public AnnotationSpec build() { |
239 | for (String name : members.keySet()) { | |
240 | checkNotNull(name, "name == null"); | |
241 | checkArgument(SourceVersion.isName(name), "not a valid name: %s", name); | |
242 | } | |
240 | 243 | return new AnnotationSpec(this); |
241 | 244 | } |
242 | 245 | } |
19 | 19 | import java.util.Collections; |
20 | 20 | import java.util.List; |
21 | 21 | import java.util.Map; |
22 | import java.util.Objects; | |
22 | 23 | import javax.lang.model.element.Element; |
23 | 24 | import javax.lang.model.element.PackageElement; |
24 | 25 | import javax.lang.model.element.TypeElement; |
31 | 32 | public final class ClassName extends TypeName implements Comparable<ClassName> { |
32 | 33 | public static final ClassName OBJECT = ClassName.get(Object.class); |
33 | 34 | |
35 | /** The name representing the default Java package. */ | |
36 | private static final String NO_PACKAGE = ""; | |
37 | ||
34 | 38 | /** The package name of this class, or "" if this is in the default package. */ |
35 | 39 | final String packageName; |
36 | 40 | |
39 | 43 | |
40 | 44 | /** This class name, like "Entry" for java.util.Map.Entry. */ |
41 | 45 | final String simpleName; |
46 | ||
47 | private List<String> simpleNames; | |
42 | 48 | |
43 | 49 | /** The full class name like "java.util.Map.Entry". */ |
44 | 50 | final String canonicalName; |
50 | 56 | private ClassName(String packageName, ClassName enclosingClassName, String simpleName, |
51 | 57 | List<AnnotationSpec> annotations) { |
52 | 58 | super(annotations); |
53 | this.packageName = packageName; | |
59 | this.packageName = Objects.requireNonNull(packageName, "packageName == null"); | |
54 | 60 | this.enclosingClassName = enclosingClassName; |
55 | 61 | this.simpleName = simpleName; |
56 | 62 | this.canonicalName = enclosingClassName != null |
107 | 113 | } |
108 | 114 | |
109 | 115 | public List<String> simpleNames() { |
110 | List<String> simpleNames = new ArrayList<>(); | |
111 | if (enclosingClassName != null) { | |
112 | simpleNames.addAll(enclosingClassName().simpleNames()); | |
113 | } | |
114 | simpleNames.add(simpleName); | |
116 | if (simpleNames != null) { | |
117 | return simpleNames; | |
118 | } | |
119 | ||
120 | if (enclosingClassName == null) { | |
121 | simpleNames = Collections.singletonList(simpleName); | |
122 | } else { | |
123 | List<String> mutableNames = new ArrayList<>(); | |
124 | mutableNames.addAll(enclosingClassName().simpleNames()); | |
125 | mutableNames.add(simpleName); | |
126 | simpleNames = Collections.unmodifiableList(mutableNames); | |
127 | } | |
115 | 128 | return simpleNames; |
116 | 129 | } |
117 | 130 | |
135 | 148 | /** Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}. */ |
136 | 149 | public String simpleName() { |
137 | 150 | return simpleName; |
151 | } | |
152 | ||
153 | /** | |
154 | * Returns the full class name of this class. | |
155 | * Like {@code "java.util.Map.Entry"} for {@link Map.Entry}. | |
156 | * */ | |
157 | public String canonicalName() { | |
158 | return canonicalName; | |
138 | 159 | } |
139 | 160 | |
140 | 161 | public static ClassName get(Class<?> clazz) { |
154 | 175 | if (clazz.getEnclosingClass() == null) { |
155 | 176 | // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295 |
156 | 177 | int lastDot = clazz.getName().lastIndexOf('.'); |
157 | String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : null; | |
178 | String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : NO_PACKAGE; | |
158 | 179 | return new ClassName(packageName, null, name); |
159 | 180 | } |
160 | 181 | |
176 | 197 | p = classNameString.indexOf('.', p) + 1; |
177 | 198 | checkArgument(p != 0, "couldn't make a guess for %s", classNameString); |
178 | 199 | } |
179 | String packageName = p == 0 ? "" : classNameString.substring(0, p - 1); | |
200 | String packageName = p == 0 ? NO_PACKAGE : classNameString.substring(0, p - 1); | |
180 | 201 | |
181 | 202 | // Add class names like "Map" and "Entry". |
182 | 203 | ClassName className = null; |
188 | 188 | while (p < format.length()) { |
189 | 189 | int nextP = format.indexOf("$", p); |
190 | 190 | if (nextP == -1) { |
191 | formatParts.add(format.substring(p, format.length())); | |
191 | formatParts.add(format.substring(p)); | |
192 | 192 | break; |
193 | 193 | } |
194 | 194 | |
423 | 423 | return this; |
424 | 424 | } |
425 | 425 | |
426 | public Builder clear() { | |
427 | formatParts.clear(); | |
428 | args.clear(); | |
429 | return this; | |
430 | } | |
431 | ||
426 | 432 | public CodeBlock build() { |
427 | 433 | return new CodeBlock(this); |
428 | 434 | } |
53 | 53 | private final List<TypeSpec> typeSpecStack = new ArrayList<>(); |
54 | 54 | private final Set<String> staticImportClassNames; |
55 | 55 | private final Set<String> staticImports; |
56 | private final Set<String> alwaysQualify; | |
56 | 57 | private final Map<String, ClassName> importedTypes; |
57 | 58 | private final Map<String, ClassName> importableTypes = new LinkedHashMap<>(); |
58 | 59 | private final Set<String> referencedNames = new LinkedHashSet<>(); |
60 | private final Multiset<String> currentTypeVariables = new Multiset<>(); | |
59 | 61 | private boolean trailingNewline; |
60 | 62 | |
61 | 63 | /** |
66 | 68 | int statementLine = -1; |
67 | 69 | |
68 | 70 | CodeWriter(Appendable out) { |
69 | this(out, " ", Collections.emptySet()); | |
70 | } | |
71 | ||
72 | CodeWriter(Appendable out, String indent, Set<String> staticImports) { | |
73 | this(out, indent, Collections.emptyMap(), staticImports); | |
74 | } | |
75 | ||
76 | CodeWriter(Appendable out, String indent, Map<String, ClassName> importedTypes, | |
77 | Set<String> staticImports) { | |
71 | this(out, " ", Collections.emptySet(), Collections.emptySet()); | |
72 | } | |
73 | ||
74 | CodeWriter(Appendable out, String indent, Set<String> staticImports, Set<String> alwaysQualify) { | |
75 | this(out, indent, Collections.emptyMap(), staticImports, alwaysQualify); | |
76 | } | |
77 | ||
78 | CodeWriter(Appendable out, | |
79 | String indent, | |
80 | Map<String, ClassName> importedTypes, | |
81 | Set<String> staticImports, | |
82 | Set<String> alwaysQualify) { | |
78 | 83 | this.out = new LineWrapper(out, indent, 100); |
79 | 84 | this.indent = checkNotNull(indent, "indent == null"); |
80 | 85 | this.importedTypes = checkNotNull(importedTypes, "importedTypes == null"); |
81 | 86 | this.staticImports = checkNotNull(staticImports, "staticImports == null"); |
87 | this.alwaysQualify = checkNotNull(alwaysQualify, "alwaysQualify == null"); | |
82 | 88 | this.staticImportClassNames = new LinkedHashSet<>(); |
83 | 89 | for (String signature : staticImports) { |
84 | 90 | staticImportClassNames.add(signature.substring(0, signature.lastIndexOf('.'))); |
147 | 153 | emit("/**\n"); |
148 | 154 | javadoc = true; |
149 | 155 | try { |
150 | emit(javadocCodeBlock); | |
156 | emit(javadocCodeBlock, true); | |
151 | 157 | } finally { |
152 | 158 | javadoc = false; |
153 | 159 | } |
185 | 191 | */ |
186 | 192 | public void emitTypeVariables(List<TypeVariableName> typeVariables) throws IOException { |
187 | 193 | if (typeVariables.isEmpty()) return; |
194 | ||
195 | typeVariables.forEach(typeVariable -> currentTypeVariables.add(typeVariable.name)); | |
188 | 196 | |
189 | 197 | emit("<"); |
190 | 198 | boolean firstTypeVariable = true; |
202 | 210 | emit(">"); |
203 | 211 | } |
204 | 212 | |
213 | public void popTypeVariables(List<TypeVariableName> typeVariables) throws IOException { | |
214 | typeVariables.forEach(typeVariable -> currentTypeVariables.remove(typeVariable.name)); | |
215 | } | |
216 | ||
205 | 217 | public CodeWriter emit(String s) throws IOException { |
206 | 218 | return emitAndIndent(s); |
207 | 219 | } |
211 | 223 | } |
212 | 224 | |
213 | 225 | public CodeWriter emit(CodeBlock codeBlock) throws IOException { |
226 | return emit(codeBlock, false); | |
227 | } | |
228 | ||
229 | public CodeWriter emit(CodeBlock codeBlock, boolean ensureTrailingNewline) throws IOException { | |
214 | 230 | int a = 0; |
215 | 231 | ClassName deferredTypeName = null; // used by "import static" logic |
216 | 232 | ListIterator<String> partIterator = codeBlock.formatParts.listIterator(); |
299 | 315 | break; |
300 | 316 | } |
301 | 317 | } |
318 | if (ensureTrailingNewline && out.lastChar() != '\n') { | |
319 | emit("\n"); | |
320 | } | |
302 | 321 | return this; |
303 | 322 | } |
304 | 323 | |
352 | 371 | * names visible due to inheritance. |
353 | 372 | */ |
354 | 373 | String lookupName(ClassName className) { |
374 | // If the top level simple name is masked by a current type variable, use the canonical name. | |
375 | String topLevelSimpleName = className.topLevelClassName().simpleName(); | |
376 | if (currentTypeVariables.contains(topLevelSimpleName)) { | |
377 | return className.canonicalName; | |
378 | } | |
379 | ||
355 | 380 | // Find the shortest suffix of className that resolves to className. This uses both local type |
356 | 381 | // names (so `Entry` in `Map` refers to `Map.Entry`). Also uses imports. |
357 | 382 | boolean nameResolved = false; |
373 | 398 | |
374 | 399 | // If the class is in the same package, we're done. |
375 | 400 | if (Objects.equals(packageName, className.packageName())) { |
376 | referencedNames.add(className.topLevelClassName().simpleName()); | |
401 | referencedNames.add(topLevelSimpleName); | |
377 | 402 | return join(".", className.simpleNames()); |
378 | 403 | } |
379 | 404 | |
387 | 412 | |
388 | 413 | private void importableType(ClassName className) { |
389 | 414 | if (className.packageName().isEmpty()) { |
415 | return; | |
416 | } else if (alwaysQualify.contains(className.simpleName)) { | |
417 | // TODO what about nested types like java.util.Map.Entry? | |
390 | 418 | return; |
391 | 419 | } |
392 | 420 | ClassName topLevelClassName = className.topLevelClassName(); |
406 | 434 | // Match a child of the current (potentially nested) class. |
407 | 435 | for (int i = typeSpecStack.size() - 1; i >= 0; i--) { |
408 | 436 | TypeSpec typeSpec = typeSpecStack.get(i); |
409 | for (TypeSpec visibleChild : typeSpec.typeSpecs) { | |
410 | if (Objects.equals(visibleChild.name, simpleName)) { | |
411 | return stackClassName(i, simpleName); | |
412 | } | |
437 | if (typeSpec.nestedTypesSimpleNames.contains(simpleName)) { | |
438 | return stackClassName(i, simpleName); | |
413 | 439 | } |
414 | 440 | } |
415 | 441 | |
442 | 468 | */ |
443 | 469 | CodeWriter emitAndIndent(String s) throws IOException { |
444 | 470 | boolean first = true; |
445 | for (String line : s.split("\n", -1)) { | |
471 | for (String line : s.split("\\R", -1)) { | |
446 | 472 | // Emit a newline character. Make sure blank lines in Javadoc & comments look good. |
447 | 473 | if (!first) { |
448 | 474 | if ((javadoc || comment) && trailingNewline) { |
493 | 519 | result.keySet().removeAll(referencedNames); |
494 | 520 | return result; |
495 | 521 | } |
522 | ||
523 | // A makeshift multi-set implementation | |
524 | private static final class Multiset<T> { | |
525 | private final Map<T, Integer> map = new LinkedHashMap<>(); | |
526 | ||
527 | void add(T t) { | |
528 | int count = map.getOrDefault(t, 0); | |
529 | map.put(t, count + 1); | |
530 | } | |
531 | ||
532 | void remove(T t) { | |
533 | int count = map.getOrDefault(t, 0); | |
534 | if (count == 0) { | |
535 | throw new IllegalStateException(t + " is not in the multiset"); | |
536 | } | |
537 | map.put(t, count - 1); | |
538 | } | |
539 | ||
540 | boolean contains(T t) { | |
541 | return map.getOrDefault(t, 0) > 0; | |
542 | } | |
543 | } | |
496 | 544 | } |
110 | 110 | private final String name; |
111 | 111 | |
112 | 112 | private final CodeBlock.Builder javadoc = CodeBlock.builder(); |
113 | private final List<AnnotationSpec> annotations = new ArrayList<>(); | |
114 | private final List<Modifier> modifiers = new ArrayList<>(); | |
115 | 113 | private CodeBlock initializer = null; |
114 | ||
115 | public final List<AnnotationSpec> annotations = new ArrayList<>(); | |
116 | public final List<Modifier> modifiers = new ArrayList<>(); | |
116 | 117 | |
117 | 118 | private Builder(TypeName type, String name) { |
118 | 119 | this.type = type; |
21 | 21 | import java.io.OutputStreamWriter; |
22 | 22 | import java.io.Writer; |
23 | 23 | import java.net.URI; |
24 | import java.nio.charset.Charset; | |
24 | 25 | import java.nio.file.Files; |
25 | 26 | import java.nio.file.Path; |
26 | 27 | import java.util.Arrays; |
27 | 28 | import java.util.Collections; |
29 | import java.util.LinkedHashSet; | |
28 | 30 | import java.util.List; |
29 | 31 | import java.util.Map; |
30 | 32 | import java.util.Set; |
58 | 60 | public final TypeSpec typeSpec; |
59 | 61 | public final boolean skipJavaLangImports; |
60 | 62 | private final Set<String> staticImports; |
63 | private final Set<String> alwaysQualify; | |
61 | 64 | private final String indent; |
62 | 65 | |
63 | 66 | private JavaFile(Builder builder) { |
67 | 70 | this.skipJavaLangImports = builder.skipJavaLangImports; |
68 | 71 | this.staticImports = Util.immutableSet(builder.staticImports); |
69 | 72 | this.indent = builder.indent; |
73 | ||
74 | Set<String> alwaysQualifiedNames = new LinkedHashSet<>(); | |
75 | fillAlwaysQualifiedNames(builder.typeSpec, alwaysQualifiedNames); | |
76 | this.alwaysQualify = Util.immutableSet(alwaysQualifiedNames); | |
77 | } | |
78 | ||
79 | private void fillAlwaysQualifiedNames(TypeSpec spec, Set<String> alwaysQualifiedNames) { | |
80 | alwaysQualifiedNames.addAll(spec.alwaysQualifiedNames); | |
81 | for (TypeSpec nested : spec.typeSpecs) { | |
82 | fillAlwaysQualifiedNames(nested, alwaysQualifiedNames); | |
83 | } | |
70 | 84 | } |
71 | 85 | |
72 | 86 | public void writeTo(Appendable out) throws IOException { |
73 | 87 | // First pass: emit the entire class, just to collect the types we'll need to import. |
74 | CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports); | |
88 | CodeWriter importsCollector = new CodeWriter( | |
89 | NULL_APPENDABLE, | |
90 | indent, | |
91 | staticImports, | |
92 | alwaysQualify | |
93 | ); | |
75 | 94 | emit(importsCollector); |
76 | 95 | Map<String, ClassName> suggestedImports = importsCollector.suggestedImports(); |
77 | 96 | |
78 | 97 | // Second pass: write the code, taking advantage of the imports. |
79 | CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports); | |
98 | CodeWriter codeWriter | |
99 | = new CodeWriter(out, indent, suggestedImports, staticImports, alwaysQualify); | |
80 | 100 | emit(codeWriter); |
81 | 101 | } |
82 | 102 | |
83 | 103 | /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */ |
84 | 104 | public void writeTo(Path directory) throws IOException { |
105 | writeToPath(directory); | |
106 | } | |
107 | ||
108 | /** | |
109 | * Writes this to {@code directory} with the provided {@code charset} using the standard directory | |
110 | * structure. | |
111 | */ | |
112 | public void writeTo(Path directory, Charset charset) throws IOException { | |
113 | writeToPath(directory, charset); | |
114 | } | |
115 | ||
116 | /** | |
117 | * Writes this to {@code directory} as UTF-8 using the standard directory structure. | |
118 | * Returns the {@link Path} instance to which source is actually written. | |
119 | */ | |
120 | public Path writeToPath(Path directory) throws IOException { | |
121 | return writeToPath(directory, UTF_8); | |
122 | } | |
123 | ||
124 | /** | |
125 | * Writes this to {@code directory} with the provided {@code charset} using the standard directory | |
126 | * structure. | |
127 | * Returns the {@link Path} instance to which source is actually written. | |
128 | */ | |
129 | public Path writeToPath(Path directory, Charset charset) throws IOException { | |
85 | 130 | checkArgument(Files.notExists(directory) || Files.isDirectory(directory), |
86 | 131 | "path %s exists but is not a directory.", directory); |
87 | 132 | Path outputDirectory = directory; |
93 | 138 | } |
94 | 139 | |
95 | 140 | Path outputPath = outputDirectory.resolve(typeSpec.name + ".java"); |
96 | try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), UTF_8)) { | |
141 | try (Writer writer = new OutputStreamWriter(Files.newOutputStream(outputPath), charset)) { | |
97 | 142 | writeTo(writer); |
98 | 143 | } |
144 | ||
145 | return outputPath; | |
99 | 146 | } |
100 | 147 | |
101 | 148 | /** Writes this to {@code directory} as UTF-8 using the standard directory structure. */ |
102 | 149 | public void writeTo(File directory) throws IOException { |
103 | 150 | writeTo(directory.toPath()); |
151 | } | |
152 | ||
153 | /** | |
154 | * Writes this to {@code directory} as UTF-8 using the standard directory structure. | |
155 | * Returns the {@link File} instance to which source is actually written. | |
156 | */ | |
157 | public File writeToFile(File directory) throws IOException { | |
158 | final Path outputPath = writeToPath(directory.toPath()); | |
159 | return outputPath.toFile(); | |
104 | 160 | } |
105 | 161 | |
106 | 162 | /** Writes this to {@code filer}. */ |
143 | 199 | |
144 | 200 | int importedTypesCount = 0; |
145 | 201 | for (ClassName className : new TreeSet<>(codeWriter.importedTypes().values())) { |
146 | if (skipJavaLangImports && className.packageName().equals("java.lang")) continue; | |
202 | // TODO what about nested types like java.util.Map.Entry? | |
203 | if (skipJavaLangImports | |
204 | && className.packageName().equals("java.lang") | |
205 | && !alwaysQualify.contains(className.simpleName)) { | |
206 | continue; | |
207 | } | |
147 | 208 | codeWriter.emit("import $L;\n", className.withoutAnnotations()); |
148 | 209 | importedTypesCount++; |
149 | 210 | } |
215 | 276 | private final String packageName; |
216 | 277 | private final TypeSpec typeSpec; |
217 | 278 | private final CodeBlock.Builder fileComment = CodeBlock.builder(); |
218 | private final Set<String> staticImports = new TreeSet<>(); | |
219 | 279 | private boolean skipJavaLangImports; |
220 | 280 | private String indent = " "; |
281 | ||
282 | public final Set<String> staticImports = new TreeSet<>(); | |
221 | 283 | |
222 | 284 | private Builder(String packageName, TypeSpec typeSpec) { |
223 | 285 | this.packageName = packageName; |
23 | 23 | * or soft-wrapping spaces using {@link #wrappingSpace}. |
24 | 24 | */ |
25 | 25 | final class LineWrapper { |
26 | private final Appendable out; | |
26 | private final RecordingAppendable out; | |
27 | 27 | private final String indent; |
28 | 28 | private final int columnLimit; |
29 | 29 | private boolean closed; |
46 | 46 | |
47 | 47 | LineWrapper(Appendable out, String indent, int columnLimit) { |
48 | 48 | checkNotNull(out, "out == null"); |
49 | this.out = out; | |
49 | this.out = new RecordingAppendable(out); | |
50 | 50 | this.indent = indent; |
51 | 51 | this.columnLimit = columnLimit; |
52 | } | |
53 | ||
54 | /** @return the last emitted char or {@link Character#MIN_VALUE} if nothing emitted yet. */ | |
55 | char lastChar() { | |
56 | return out.lastChar; | |
52 | 57 | } |
53 | 58 | |
54 | 59 | /** Emit {@code s}. This may be buffered to permit line wraps to be inserted. */ |
133 | 138 | private enum FlushType { |
134 | 139 | WRAP, SPACE, EMPTY; |
135 | 140 | } |
141 | ||
142 | /** A delegating {@link Appendable} that records info about the chars passing through it. */ | |
143 | static final class RecordingAppendable implements Appendable { | |
144 | private final Appendable delegate; | |
145 | ||
146 | char lastChar = Character.MIN_VALUE; | |
147 | ||
148 | RecordingAppendable(Appendable delegate) { | |
149 | this.delegate = delegate; | |
150 | } | |
151 | ||
152 | @Override public Appendable append(CharSequence csq) throws IOException { | |
153 | int length = csq.length(); | |
154 | if (length != 0) { | |
155 | lastChar = csq.charAt(length - 1); | |
156 | } | |
157 | return delegate.append(csq); | |
158 | } | |
159 | ||
160 | @Override public Appendable append(CharSequence csq, int start, int end) throws IOException { | |
161 | CharSequence sub = csq.subSequence(start, end); | |
162 | return append(sub); | |
163 | } | |
164 | ||
165 | @Override public Appendable append(char c) throws IOException { | |
166 | lastChar = c; | |
167 | return delegate.append(c); | |
168 | } | |
169 | } | |
136 | 170 | } |
23 | 23 | import java.util.List; |
24 | 24 | import java.util.Map; |
25 | 25 | import java.util.Set; |
26 | import java.util.stream.Collectors; | |
26 | 27 | import javax.lang.model.SourceVersion; |
27 | 28 | import javax.lang.model.element.Element; |
28 | 29 | import javax.lang.model.element.ExecutableElement; |
81 | 82 | |
82 | 83 | void emit(CodeWriter codeWriter, String enclosingName, Set<Modifier> implicitModifiers) |
83 | 84 | throws IOException { |
84 | codeWriter.emitJavadoc(javadoc); | |
85 | codeWriter.emitJavadoc(javadocWithParameters()); | |
85 | 86 | codeWriter.emitAnnotations(annotations, false); |
86 | 87 | codeWriter.emitModifiers(modifiers, implicitModifiers); |
87 | 88 | |
131 | 132 | codeWriter.emit(" {\n"); |
132 | 133 | |
133 | 134 | codeWriter.indent(); |
134 | codeWriter.emit(code); | |
135 | codeWriter.emit(code, true); | |
135 | 136 | codeWriter.unindent(); |
136 | 137 | |
137 | 138 | codeWriter.emit("}\n"); |
138 | 139 | } |
140 | codeWriter.popTypeVariables(typeVariables); | |
141 | } | |
142 | ||
143 | private CodeBlock javadocWithParameters() { | |
144 | CodeBlock.Builder builder = javadoc.toBuilder(); | |
145 | boolean emitTagNewline = true; | |
146 | for (ParameterSpec parameterSpec : parameters) { | |
147 | if (!parameterSpec.javadoc.isEmpty()) { | |
148 | // Emit a new line before @param section only if the method javadoc is present. | |
149 | if (emitTagNewline && !javadoc.isEmpty()) builder.add("\n"); | |
150 | emitTagNewline = false; | |
151 | builder.add("@param $L $L", parameterSpec.name, parameterSpec.javadoc); | |
152 | } | |
153 | } | |
154 | return builder.build(); | |
139 | 155 | } |
140 | 156 | |
141 | 157 | public boolean hasModifier(Modifier modifier) { |
216 | 232 | } |
217 | 233 | |
218 | 234 | methodBuilder.returns(TypeName.get(method.getReturnType())); |
219 | methodBuilder.addParameters(ParameterSpec.parametersOf(method)); | |
235 | // Copying parameter annotations from the overridden method can be incorrect so we're | |
236 | // deliberately dropping them. See https://github.com/square/javapoet/issues/482. | |
237 | methodBuilder.addParameters(ParameterSpec.parametersOf(method) | |
238 | .stream() | |
239 | .map(parameterSpec -> { | |
240 | ParameterSpec.Builder builder = parameterSpec.toBuilder(); | |
241 | builder.annotations.clear(); | |
242 | return builder.build(); | |
243 | }) | |
244 | .collect(Collectors.toList())); | |
220 | 245 | methodBuilder.varargs(method.isVarArgs()); |
221 | 246 | |
222 | 247 | for (TypeMirror thrownType : method.getThrownTypes()) { |
276 | 301 | } |
277 | 302 | |
278 | 303 | public static final class Builder { |
279 | private final String name; | |
304 | private String name; | |
280 | 305 | |
281 | 306 | private final CodeBlock.Builder javadoc = CodeBlock.builder(); |
282 | private final List<AnnotationSpec> annotations = new ArrayList<>(); | |
283 | private final List<Modifier> modifiers = new ArrayList<>(); | |
284 | private List<TypeVariableName> typeVariables = new ArrayList<>(); | |
285 | 307 | private TypeName returnType; |
286 | private final List<ParameterSpec> parameters = new ArrayList<>(); | |
287 | 308 | private final Set<TypeName> exceptions = new LinkedHashSet<>(); |
288 | 309 | private final CodeBlock.Builder code = CodeBlock.builder(); |
289 | 310 | private boolean varargs; |
290 | 311 | private CodeBlock defaultValue; |
291 | 312 | |
313 | public final List<TypeVariableName> typeVariables = new ArrayList<>(); | |
314 | public final List<AnnotationSpec> annotations = new ArrayList<>(); | |
315 | public final List<Modifier> modifiers = new ArrayList<>(); | |
316 | public final List<ParameterSpec> parameters = new ArrayList<>(); | |
317 | ||
292 | 318 | private Builder(String name) { |
319 | setName(name); | |
320 | } | |
321 | ||
322 | public Builder setName(String name) { | |
293 | 323 | checkNotNull(name, "name == null"); |
294 | 324 | checkArgument(name.equals(CONSTRUCTOR) || SourceVersion.isName(name), |
295 | 325 | "not a valid name: %s", name); |
296 | 326 | this.name = name; |
297 | 327 | this.returnType = name.equals(CONSTRUCTOR) ? null : TypeName.VOID; |
328 | return this; | |
298 | 329 | } |
299 | 330 | |
300 | 331 | public Builder addJavadoc(String format, Object... args) { |
453 | 484 | } |
454 | 485 | |
455 | 486 | /** |
487 | * @param codeBlock the control flow construct and its code, such as "if (foo == 5)". | |
488 | * Shouldn't contain braces or newline characters. | |
489 | */ | |
490 | public Builder beginControlFlow(CodeBlock codeBlock) { | |
491 | return beginControlFlow("$L", codeBlock); | |
492 | } | |
493 | ||
494 | /** | |
456 | 495 | * @param controlFlow the control flow construct and its code, such as "else if (foo == 10)". |
457 | 496 | * Shouldn't contain braces or newline characters. |
458 | 497 | */ |
459 | 498 | public Builder nextControlFlow(String controlFlow, Object... args) { |
460 | 499 | code.nextControlFlow(controlFlow, args); |
461 | 500 | return this; |
501 | } | |
502 | ||
503 | /** | |
504 | * @param codeBlock the control flow construct and its code, such as "else if (foo == 10)". | |
505 | * Shouldn't contain braces or newline characters. | |
506 | */ | |
507 | public Builder nextControlFlow(CodeBlock codeBlock) { | |
508 | return nextControlFlow("$L", codeBlock); | |
462 | 509 | } |
463 | 510 | |
464 | 511 | public Builder endControlFlow() { |
475 | 522 | return this; |
476 | 523 | } |
477 | 524 | |
525 | /** | |
526 | * @param codeBlock the optional control flow construct and its code, such as | |
527 | * "while(foo == 20)". Only used for "do/while" control flows. | |
528 | */ | |
529 | public Builder endControlFlow(CodeBlock codeBlock) { | |
530 | return endControlFlow("$L", codeBlock); | |
531 | } | |
532 | ||
478 | 533 | public Builder addStatement(String format, Object... args) { |
479 | 534 | code.addStatement(format, args); |
480 | 535 | return this; |
20 | 20 | import java.util.Collections; |
21 | 21 | import java.util.List; |
22 | 22 | import java.util.Set; |
23 | import java.util.stream.Collectors; | |
23 | 24 | import javax.lang.model.SourceVersion; |
25 | import javax.lang.model.element.ElementKind; | |
24 | 26 | import javax.lang.model.element.ExecutableElement; |
25 | 27 | import javax.lang.model.element.Modifier; |
26 | 28 | import javax.lang.model.element.VariableElement; |
34 | 36 | public final List<AnnotationSpec> annotations; |
35 | 37 | public final Set<Modifier> modifiers; |
36 | 38 | public final TypeName type; |
39 | public final CodeBlock javadoc; | |
37 | 40 | |
38 | 41 | private ParameterSpec(Builder builder) { |
39 | 42 | this.name = checkNotNull(builder.name, "name == null"); |
40 | 43 | this.annotations = Util.immutableList(builder.annotations); |
41 | 44 | this.modifiers = Util.immutableSet(builder.modifiers); |
42 | 45 | this.type = checkNotNull(builder.type, "type == null"); |
46 | this.javadoc = builder.javadoc.build(); | |
43 | 47 | } |
44 | 48 | |
45 | 49 | public boolean hasModifier(Modifier modifier) { |
80 | 84 | } |
81 | 85 | |
82 | 86 | public static ParameterSpec get(VariableElement element) { |
87 | checkArgument(element.getKind().equals(ElementKind.PARAMETER), "element is not a parameter"); | |
88 | ||
89 | // Copy over any annotations from element. | |
90 | List<AnnotationSpec> annotations = element.getAnnotationMirrors() | |
91 | .stream() | |
92 | .map((mirror) -> AnnotationSpec.get(mirror)) | |
93 | .collect(Collectors.toList()); | |
94 | ||
83 | 95 | TypeName type = TypeName.get(element.asType()); |
84 | 96 | String name = element.getSimpleName().toString(); |
85 | 97 | return ParameterSpec.builder(type, name) |
86 | 98 | .addModifiers(element.getModifiers()) |
99 | .addAnnotations(annotations) | |
87 | 100 | .build(); |
88 | 101 | } |
89 | 102 | |
120 | 133 | public static final class Builder { |
121 | 134 | private final TypeName type; |
122 | 135 | private final String name; |
136 | private final CodeBlock.Builder javadoc = CodeBlock.builder(); | |
123 | 137 | |
124 | private final List<AnnotationSpec> annotations = new ArrayList<>(); | |
125 | private final List<Modifier> modifiers = new ArrayList<>(); | |
138 | public final List<AnnotationSpec> annotations = new ArrayList<>(); | |
139 | public final List<Modifier> modifiers = new ArrayList<>(); | |
126 | 140 | |
127 | 141 | private Builder(TypeName type, String name) { |
128 | 142 | this.type = type; |
129 | 143 | this.name = name; |
144 | } | |
145 | ||
146 | public Builder addJavadoc(String format, Object... args) { | |
147 | javadoc.add(format, args); | |
148 | return this; | |
149 | } | |
150 | ||
151 | public Builder addJavadoc(CodeBlock block) { | |
152 | javadoc.add(block); | |
153 | return this; | |
130 | 154 | } |
131 | 155 | |
132 | 156 | public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) { |
159 | 183 | public Builder addModifiers(Iterable<Modifier> modifiers) { |
160 | 184 | checkNotNull(modifiers, "modifiers == null"); |
161 | 185 | for (Modifier modifier : modifiers) { |
186 | if (!modifier.equals(Modifier.FINAL)) { | |
187 | throw new IllegalStateException("unexpected parameter modifier: " + modifier); | |
188 | } | |
162 | 189 | this.modifiers.add(modifier); |
163 | 190 | } |
164 | 191 | return this; |
43 | 43 | * identifies composite types like {@code char[]} and {@code Set<Long>}. |
44 | 44 | * |
45 | 45 | * <p>Type names are dumb identifiers only and do not model the values they name. For example, the |
46 | * type name for {@code java.lang.List} doesn't know about the {@code size()} method, the fact that | |
46 | * type name for {@code java.util.List} doesn't know about the {@code size()} method, the fact that | |
47 | 47 | * lists are collections, or even that it accepts a single type parameter. |
48 | 48 | * |
49 | 49 | * <p>Instances of this class are immutable value objects that implement {@code equals()} and {@code |
15 | 15 | package com.squareup.javapoet; |
16 | 16 | |
17 | 17 | import java.io.IOException; |
18 | import java.lang.reflect.ParameterizedType; | |
18 | 19 | import java.lang.reflect.Type; |
19 | 20 | import java.util.ArrayList; |
20 | 21 | import java.util.Arrays; |
21 | 22 | import java.util.Collections; |
22 | 23 | import java.util.EnumSet; |
24 | import java.util.HashSet; | |
23 | 25 | import java.util.Iterator; |
24 | 26 | import java.util.LinkedHashMap; |
27 | import java.util.LinkedHashSet; | |
25 | 28 | import java.util.List; |
26 | 29 | import java.util.Locale; |
27 | 30 | import java.util.Map; |
29 | 32 | import javax.lang.model.SourceVersion; |
30 | 33 | import javax.lang.model.element.Element; |
31 | 34 | import javax.lang.model.element.Modifier; |
35 | import javax.lang.model.element.TypeElement; | |
36 | import javax.lang.model.type.DeclaredType; | |
37 | import javax.lang.model.type.NoType; | |
38 | import javax.lang.model.type.TypeMirror; | |
39 | import javax.lang.model.util.ElementFilter; | |
32 | 40 | |
33 | 41 | import static com.squareup.javapoet.Util.checkArgument; |
34 | 42 | import static com.squareup.javapoet.Util.checkNotNull; |
52 | 60 | public final CodeBlock initializerBlock; |
53 | 61 | public final List<MethodSpec> methodSpecs; |
54 | 62 | public final List<TypeSpec> typeSpecs; |
63 | final Set<String> nestedTypesSimpleNames; | |
55 | 64 | public final List<Element> originatingElements; |
65 | public final Set<String> alwaysQualifiedNames; | |
56 | 66 | |
57 | 67 | private TypeSpec(Builder builder) { |
58 | 68 | this.kind = builder.kind; |
70 | 80 | this.initializerBlock = builder.initializerBlock.build(); |
71 | 81 | this.methodSpecs = Util.immutableList(builder.methodSpecs); |
72 | 82 | this.typeSpecs = Util.immutableList(builder.typeSpecs); |
73 | ||
83 | this.alwaysQualifiedNames = Util.immutableSet(builder.alwaysQualifiedNames); | |
84 | ||
85 | nestedTypesSimpleNames = new HashSet<>(builder.typeSpecs.size()); | |
74 | 86 | List<Element> originatingElementsMutable = new ArrayList<>(); |
75 | 87 | originatingElementsMutable.addAll(builder.originatingElements); |
76 | 88 | for (TypeSpec typeSpec : builder.typeSpecs) { |
89 | nestedTypesSimpleNames.add(typeSpec.name); | |
77 | 90 | originatingElementsMutable.addAll(typeSpec.originatingElements); |
78 | 91 | } |
92 | ||
79 | 93 | this.originatingElements = Util.immutableList(originatingElementsMutable); |
80 | 94 | } |
81 | 95 | |
101 | 115 | this.methodSpecs = Collections.emptyList(); |
102 | 116 | this.typeSpecs = Collections.emptyList(); |
103 | 117 | this.originatingElements = Collections.emptyList(); |
118 | this.nestedTypesSimpleNames = Collections.emptySet(); | |
119 | this.alwaysQualifiedNames = Collections.emptySet(); | |
104 | 120 | } |
105 | 121 | |
106 | 122 | public boolean hasModifier(Modifier modifier) { |
132 | 148 | } |
133 | 149 | |
134 | 150 | public static Builder anonymousClassBuilder(String typeArgumentsFormat, Object... args) { |
135 | return anonymousClassBuilder(CodeBlock.builder() | |
136 | .add(typeArgumentsFormat, args) | |
137 | .build()); | |
151 | return anonymousClassBuilder(CodeBlock.of(typeArgumentsFormat, args)); | |
138 | 152 | } |
139 | 153 | |
140 | 154 | public static Builder anonymousClassBuilder(CodeBlock typeArguments) { |
163 | 177 | builder.typeSpecs.addAll(typeSpecs); |
164 | 178 | builder.initializerBlock.add(initializerBlock); |
165 | 179 | builder.staticBlock.add(staticBlock); |
180 | builder.originatingElements.addAll(originatingElements); | |
181 | builder.alwaysQualifiedNames.addAll(alwaysQualifiedNames); | |
166 | 182 | return builder; |
167 | 183 | } |
168 | 184 | |
315 | 331 | |
316 | 332 | codeWriter.unindent(); |
317 | 333 | codeWriter.popType(); |
334 | codeWriter.popTypeVariables(typeVariables); | |
318 | 335 | |
319 | 336 | codeWriter.emit("}"); |
320 | 337 | if (enumName == null && anonymousTypeArguments == null) { |
394 | 411 | private final CodeBlock anonymousTypeArguments; |
395 | 412 | |
396 | 413 | private final CodeBlock.Builder javadoc = CodeBlock.builder(); |
397 | private final List<AnnotationSpec> annotations = new ArrayList<>(); | |
398 | private final List<Modifier> modifiers = new ArrayList<>(); | |
399 | private final List<TypeVariableName> typeVariables = new ArrayList<>(); | |
400 | 414 | private TypeName superclass = ClassName.OBJECT; |
401 | private final List<TypeName> superinterfaces = new ArrayList<>(); | |
402 | private final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>(); | |
403 | private final List<FieldSpec> fieldSpecs = new ArrayList<>(); | |
404 | 415 | private final CodeBlock.Builder staticBlock = CodeBlock.builder(); |
405 | 416 | private final CodeBlock.Builder initializerBlock = CodeBlock.builder(); |
406 | private final List<MethodSpec> methodSpecs = new ArrayList<>(); | |
407 | private final List<TypeSpec> typeSpecs = new ArrayList<>(); | |
408 | private final List<Element> originatingElements = new ArrayList<>(); | |
417 | ||
418 | public final Map<String, TypeSpec> enumConstants = new LinkedHashMap<>(); | |
419 | public final List<AnnotationSpec> annotations = new ArrayList<>(); | |
420 | public final List<Modifier> modifiers = new ArrayList<>(); | |
421 | public final List<TypeVariableName> typeVariables = new ArrayList<>(); | |
422 | public final List<TypeName> superinterfaces = new ArrayList<>(); | |
423 | public final List<FieldSpec> fieldSpecs = new ArrayList<>(); | |
424 | public final List<MethodSpec> methodSpecs = new ArrayList<>(); | |
425 | public final List<TypeSpec> typeSpecs = new ArrayList<>(); | |
426 | public final List<Element> originatingElements = new ArrayList<>(); | |
427 | public final Set<String> alwaysQualifiedNames = new LinkedHashSet<>(); | |
409 | 428 | |
410 | 429 | private Builder(Kind kind, String name, |
411 | 430 | CodeBlock anonymousTypeArguments) { |
448 | 467 | } |
449 | 468 | |
450 | 469 | public Builder addModifiers(Modifier... modifiers) { |
451 | checkState(anonymousTypeArguments == null, "forbidden on anonymous types."); | |
452 | for (Modifier modifier : modifiers) { | |
453 | checkArgument(modifier != null, "modifiers contain null"); | |
454 | this.modifiers.add(modifier); | |
455 | } | |
470 | Collections.addAll(this.modifiers, modifiers); | |
456 | 471 | return this; |
457 | 472 | } |
458 | 473 | |
459 | 474 | public Builder addTypeVariables(Iterable<TypeVariableName> typeVariables) { |
460 | checkState(anonymousTypeArguments == null, "forbidden on anonymous types."); | |
461 | 475 | checkArgument(typeVariables != null, "typeVariables == null"); |
462 | 476 | for (TypeVariableName typeVariable : typeVariables) { |
463 | 477 | this.typeVariables.add(typeVariable); |
466 | 480 | } |
467 | 481 | |
468 | 482 | public Builder addTypeVariable(TypeVariableName typeVariable) { |
469 | checkState(anonymousTypeArguments == null, "forbidden on anonymous types."); | |
470 | 483 | typeVariables.add(typeVariable); |
471 | 484 | return this; |
472 | 485 | } |
481 | 494 | } |
482 | 495 | |
483 | 496 | public Builder superclass(Type superclass) { |
484 | return superclass(TypeName.get(superclass)); | |
497 | return superclass(superclass, true); | |
498 | } | |
499 | ||
500 | public Builder superclass(Type superclass, boolean avoidNestedTypeNameClashes) { | |
501 | superclass(TypeName.get(superclass)); | |
502 | if (avoidNestedTypeNameClashes) { | |
503 | Class<?> clazz = getRawType(superclass); | |
504 | if (clazz != null) { | |
505 | avoidClashesWithNestedClasses(clazz); | |
506 | } | |
507 | } | |
508 | return this; | |
509 | } | |
510 | ||
511 | public Builder superclass(TypeMirror superclass) { | |
512 | return superclass(superclass, true); | |
513 | } | |
514 | ||
515 | public Builder superclass(TypeMirror superclass, boolean avoidNestedTypeNameClashes) { | |
516 | superclass(TypeName.get(superclass)); | |
517 | if (avoidNestedTypeNameClashes && superclass instanceof DeclaredType) { | |
518 | TypeElement superInterfaceElement = | |
519 | (TypeElement) ((DeclaredType) superclass).asElement(); | |
520 | avoidClashesWithNestedClasses(superInterfaceElement); | |
521 | } | |
522 | return this; | |
485 | 523 | } |
486 | 524 | |
487 | 525 | public Builder addSuperinterfaces(Iterable<? extends TypeName> superinterfaces) { |
499 | 537 | } |
500 | 538 | |
501 | 539 | public Builder addSuperinterface(Type superinterface) { |
502 | return addSuperinterface(TypeName.get(superinterface)); | |
540 | return addSuperinterface(superinterface, true); | |
541 | } | |
542 | ||
543 | public Builder addSuperinterface(Type superinterface, boolean avoidNestedTypeNameClashes) { | |
544 | addSuperinterface(TypeName.get(superinterface)); | |
545 | if (avoidNestedTypeNameClashes) { | |
546 | Class<?> clazz = getRawType(superinterface); | |
547 | if (clazz != null) { | |
548 | avoidClashesWithNestedClasses(clazz); | |
549 | } | |
550 | } | |
551 | return this; | |
552 | } | |
553 | ||
554 | private Class<?> getRawType(Type type) { | |
555 | if (type instanceof Class<?>) { | |
556 | return (Class<?>) type; | |
557 | } else if (type instanceof ParameterizedType) { | |
558 | return getRawType(((ParameterizedType) type).getRawType()); | |
559 | } else { | |
560 | return null; | |
561 | } | |
562 | } | |
563 | ||
564 | public Builder addSuperinterface(TypeMirror superinterface) { | |
565 | return addSuperinterface(superinterface, true); | |
566 | } | |
567 | ||
568 | public Builder addSuperinterface(TypeMirror superinterface, | |
569 | boolean avoidNestedTypeNameClashes) { | |
570 | addSuperinterface(TypeName.get(superinterface)); | |
571 | if (avoidNestedTypeNameClashes && superinterface instanceof DeclaredType) { | |
572 | TypeElement superInterfaceElement = | |
573 | (TypeElement) ((DeclaredType) superinterface).asElement(); | |
574 | avoidClashesWithNestedClasses(superInterfaceElement); | |
575 | } | |
576 | return this; | |
503 | 577 | } |
504 | 578 | |
505 | 579 | public Builder addEnumConstant(String name) { |
507 | 581 | } |
508 | 582 | |
509 | 583 | public Builder addEnumConstant(String name, TypeSpec typeSpec) { |
510 | checkState(kind == Kind.ENUM, "%s is not enum", this.name); | |
511 | checkArgument(typeSpec.anonymousTypeArguments != null, | |
512 | "enum constants must have anonymous type arguments"); | |
513 | checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name); | |
514 | 584 | enumConstants.put(name, typeSpec); |
515 | 585 | return this; |
516 | 586 | } |
524 | 594 | } |
525 | 595 | |
526 | 596 | public Builder addField(FieldSpec fieldSpec) { |
527 | if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) { | |
528 | requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE); | |
529 | Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL); | |
530 | checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s", | |
531 | kind, name, fieldSpec.name, check); | |
532 | } | |
533 | 597 | fieldSpecs.add(fieldSpec); |
534 | 598 | return this; |
535 | 599 | } |
568 | 632 | } |
569 | 633 | |
570 | 634 | public Builder addMethod(MethodSpec methodSpec) { |
571 | if (kind == Kind.INTERFACE) { | |
572 | requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC, | |
573 | Modifier.DEFAULT); | |
574 | requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE); | |
575 | } else if (kind == Kind.ANNOTATION) { | |
576 | checkState(methodSpec.modifiers.equals(kind.implicitMethodModifiers), | |
577 | "%s %s.%s requires modifiers %s", | |
578 | kind, name, methodSpec.name, kind.implicitMethodModifiers); | |
579 | } | |
580 | if (kind != Kind.ANNOTATION) { | |
581 | checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value", | |
582 | kind, name, methodSpec.name); | |
583 | } | |
584 | if (kind != Kind.INTERFACE) { | |
585 | checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default", | |
586 | kind, name, methodSpec.name); | |
587 | } | |
588 | 635 | methodSpecs.add(methodSpec); |
589 | 636 | return this; |
590 | 637 | } |
598 | 645 | } |
599 | 646 | |
600 | 647 | public Builder addType(TypeSpec typeSpec) { |
601 | checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers), | |
602 | "%s %s.%s requires modifiers %s", kind, name, typeSpec.name, | |
603 | kind.implicitTypeModifiers); | |
604 | 648 | typeSpecs.add(typeSpec); |
605 | 649 | return this; |
606 | 650 | } |
610 | 654 | return this; |
611 | 655 | } |
612 | 656 | |
657 | public Builder alwaysQualify(String... simpleNames) { | |
658 | checkArgument(simpleNames != null, "simpleNames == null"); | |
659 | for (String name : simpleNames) { | |
660 | checkArgument( | |
661 | name != null, | |
662 | "null entry in simpleNames array: %s", | |
663 | Arrays.toString(simpleNames) | |
664 | ); | |
665 | alwaysQualifiedNames.add(name); | |
666 | } | |
667 | return this; | |
668 | } | |
669 | ||
670 | /** | |
671 | * Call this to always fully qualify any types that would conflict with possibly nested types of | |
672 | * this {@code typeElement}. For example - if the following type was passed in as the | |
673 | * typeElement: | |
674 | * | |
675 | * <pre><code> | |
676 | * class Foo { | |
677 | * class NestedTypeA { | |
678 | * | |
679 | * } | |
680 | * class NestedTypeB { | |
681 | * | |
682 | * } | |
683 | * } | |
684 | * </code></pre> | |
685 | * | |
686 | * <p> | |
687 | * Then this would add {@code "NestedTypeA"} and {@code "NestedTypeB"} as names that should | |
688 | * always be qualified via {@link #alwaysQualify(String...)}. This way they would avoid | |
689 | * possible import conflicts when this JavaFile is written. | |
690 | * | |
691 | * @param typeElement the {@link TypeElement} with nested types to avoid clashes with. | |
692 | * @return this builder instance. | |
693 | */ | |
694 | public Builder avoidClashesWithNestedClasses(TypeElement typeElement) { | |
695 | checkArgument(typeElement != null, "typeElement == null"); | |
696 | for (TypeElement nestedType : ElementFilter.typesIn(typeElement.getEnclosedElements())) { | |
697 | alwaysQualify(nestedType.getSimpleName().toString()); | |
698 | } | |
699 | TypeMirror superclass = typeElement.getSuperclass(); | |
700 | if (!(superclass instanceof NoType) && superclass instanceof DeclaredType) { | |
701 | TypeElement superclassElement = (TypeElement) ((DeclaredType) superclass).asElement(); | |
702 | avoidClashesWithNestedClasses(superclassElement); | |
703 | } | |
704 | for (TypeMirror superinterface : typeElement.getInterfaces()) { | |
705 | if (superinterface instanceof DeclaredType) { | |
706 | TypeElement superinterfaceElement | |
707 | = (TypeElement) ((DeclaredType) superinterface).asElement(); | |
708 | avoidClashesWithNestedClasses(superinterfaceElement); | |
709 | } | |
710 | } | |
711 | return this; | |
712 | } | |
713 | ||
714 | /** | |
715 | * Call this to always fully qualify any types that would conflict with possibly nested types of | |
716 | * this {@code typeElement}. For example - if the following type was passed in as the | |
717 | * typeElement: | |
718 | * | |
719 | * <pre><code> | |
720 | * class Foo { | |
721 | * class NestedTypeA { | |
722 | * | |
723 | * } | |
724 | * class NestedTypeB { | |
725 | * | |
726 | * } | |
727 | * } | |
728 | * </code></pre> | |
729 | * | |
730 | * <p> | |
731 | * Then this would add {@code "NestedTypeA"} and {@code "NestedTypeB"} as names that should | |
732 | * always be qualified via {@link #alwaysQualify(String...)}. This way they would avoid | |
733 | * possible import conflicts when this JavaFile is written. | |
734 | * | |
735 | * @param clazz the {@link Class} with nested types to avoid clashes with. | |
736 | * @return this builder instance. | |
737 | */ | |
738 | public Builder avoidClashesWithNestedClasses(Class<?> clazz) { | |
739 | checkArgument(clazz != null, "clazz == null"); | |
740 | for (Class<?> nestedType : clazz.getDeclaredClasses()) { | |
741 | alwaysQualify(nestedType.getSimpleName()); | |
742 | } | |
743 | Class<?> superclass = clazz.getSuperclass(); | |
744 | if (superclass != null && !Object.class.equals(superclass)) { | |
745 | avoidClashesWithNestedClasses(superclass); | |
746 | } | |
747 | for (Class<?> superinterface : clazz.getInterfaces()) { | |
748 | avoidClashesWithNestedClasses(superinterface); | |
749 | } | |
750 | return this; | |
751 | } | |
752 | ||
613 | 753 | public TypeSpec build() { |
754 | for (AnnotationSpec annotationSpec : annotations) { | |
755 | checkNotNull(annotationSpec, "annotationSpec == null"); | |
756 | } | |
757 | ||
758 | if (!modifiers.isEmpty()) { | |
759 | checkState(anonymousTypeArguments == null, "forbidden on anonymous types."); | |
760 | for (Modifier modifier : modifiers) { | |
761 | checkArgument(modifier != null, "modifiers contain null"); | |
762 | } | |
763 | } | |
764 | ||
614 | 765 | checkArgument(kind != Kind.ENUM || !enumConstants.isEmpty(), |
615 | 766 | "at least one enum constant is required for %s", name); |
767 | ||
768 | for (TypeName superinterface : superinterfaces) { | |
769 | checkArgument(superinterface != null, "superinterfaces contains null"); | |
770 | } | |
771 | ||
772 | if (!typeVariables.isEmpty()) { | |
773 | checkState(anonymousTypeArguments == null, | |
774 | "typevariables are forbidden on anonymous types."); | |
775 | for (TypeVariableName typeVariableName : typeVariables) { | |
776 | checkArgument(typeVariableName != null, "typeVariables contain null"); | |
777 | } | |
778 | } | |
779 | ||
780 | for (Map.Entry<String, TypeSpec> enumConstant : enumConstants.entrySet()) { | |
781 | checkState(kind == Kind.ENUM, "%s is not enum", this.name); | |
782 | checkArgument(enumConstant.getValue().anonymousTypeArguments != null, | |
783 | "enum constants must have anonymous type arguments"); | |
784 | checkArgument(SourceVersion.isName(name), "not a valid enum constant: %s", name); | |
785 | } | |
786 | ||
787 | for (FieldSpec fieldSpec : fieldSpecs) { | |
788 | if (kind == Kind.INTERFACE || kind == Kind.ANNOTATION) { | |
789 | requireExactlyOneOf(fieldSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE); | |
790 | Set<Modifier> check = EnumSet.of(Modifier.STATIC, Modifier.FINAL); | |
791 | checkState(fieldSpec.modifiers.containsAll(check), "%s %s.%s requires modifiers %s", | |
792 | kind, name, fieldSpec.name, check); | |
793 | } | |
794 | } | |
795 | ||
796 | for (MethodSpec methodSpec : methodSpecs) { | |
797 | if (kind == Kind.INTERFACE) { | |
798 | requireExactlyOneOf(methodSpec.modifiers, Modifier.ABSTRACT, Modifier.STATIC, | |
799 | Modifier.DEFAULT); | |
800 | requireExactlyOneOf(methodSpec.modifiers, Modifier.PUBLIC, Modifier.PRIVATE); | |
801 | } else if (kind == Kind.ANNOTATION) { | |
802 | checkState(methodSpec.modifiers.equals(kind.implicitMethodModifiers), | |
803 | "%s %s.%s requires modifiers %s", | |
804 | kind, name, methodSpec.name, kind.implicitMethodModifiers); | |
805 | } | |
806 | if (kind != Kind.ANNOTATION) { | |
807 | checkState(methodSpec.defaultValue == null, "%s %s.%s cannot have a default value", | |
808 | kind, name, methodSpec.name); | |
809 | } | |
810 | if (kind != Kind.INTERFACE) { | |
811 | checkState(!methodSpec.hasModifier(Modifier.DEFAULT), "%s %s.%s cannot be default", | |
812 | kind, name, methodSpec.name); | |
813 | } | |
814 | } | |
815 | ||
816 | for (TypeSpec typeSpec : typeSpecs) { | |
817 | checkArgument(typeSpec.modifiers.containsAll(kind.implicitTypeModifiers), | |
818 | "%s %s.%s requires modifiers %s", kind, name, typeSpec.name, | |
819 | kind.implicitTypeModifiers); | |
820 | } | |
616 | 821 | |
617 | 822 | boolean isAbstract = modifiers.contains(Modifier.ABSTRACT) || kind != Kind.CLASS; |
618 | 823 | for (MethodSpec methodSpec : methodSpecs) { |
0 | /* | |
1 | * Copyright (C) 2019 Square, Inc. | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | import static com.google.common.truth.Truth.assertThat; | |
17 | ||
18 | import com.squareup.javapoet.ClassName; | |
19 | import org.junit.Test; | |
20 | ||
21 | /** | |
22 | * Since it is impossible to import classes from the default package into other | |
23 | * modules, this test must live in this package. | |
24 | */ | |
25 | public final class ClassNameNoPackageTest { | |
26 | @Test public void shouldSupportClassInDefaultPackage() { | |
27 | ClassName className = ClassName.get(ClassNameNoPackageTest.class); | |
28 | assertThat(className.packageName()).isEqualTo(""); | |
29 | assertThat(className.simpleName()).isEqualTo("ClassNameNoPackageTest"); | |
30 | assertThat(className.toString()).isEqualTo("ClassNameNoPackageTest"); | |
31 | } | |
32 | } |
19 | 19 | import java.lang.annotation.Inherited; |
20 | 20 | import java.lang.annotation.Retention; |
21 | 21 | import java.lang.annotation.RetentionPolicy; |
22 | import java.util.Arrays; | |
23 | ||
22 | 24 | import javax.lang.model.element.TypeElement; |
23 | 25 | import org.junit.Rule; |
24 | 26 | import org.junit.Test; |
370 | 372 | } |
371 | 373 | } |
372 | 374 | |
375 | @Test public void modifyMembers() { | |
376 | AnnotationSpec.Builder builder = AnnotationSpec.builder(SuppressWarnings.class) | |
377 | .addMember("value", "$S", "Foo"); | |
378 | ||
379 | builder.members.clear(); | |
380 | builder.members.put("value", Arrays.asList(CodeBlock.of("$S", "Bar"))); | |
381 | ||
382 | assertThat(builder.build().toString()).isEqualTo("@java.lang.SuppressWarnings(\"Bar\")"); | |
383 | } | |
384 | ||
373 | 385 | private String toString(TypeSpec typeSpec) { |
374 | 386 | return JavaFile.builder("com.squareup.tacos", typeSpec).build().toString(); |
375 | 387 | } |
192 | 192 | assertEquals("Foo$Bar$Baz", ClassName.get("", "Foo", "Bar", "Baz").reflectionName()); |
193 | 193 | assertEquals("a.b.c.Foo$Bar$Baz", ClassName.get("a.b.c", "Foo", "Bar", "Baz").reflectionName()); |
194 | 194 | } |
195 | ||
196 | @Test | |
197 | public void canonicalName() { | |
198 | assertEquals("java.lang.Object", TypeName.OBJECT.canonicalName()); | |
199 | assertEquals("java.lang.Thread.State", ClassName.get(Thread.State.class).canonicalName()); | |
200 | assertEquals("java.util.Map.Entry", ClassName.get(Map.Entry.class).canonicalName()); | |
201 | assertEquals("Foo", ClassName.get("", "Foo").canonicalName()); | |
202 | assertEquals("Foo.Bar.Baz", ClassName.get("", "Foo", "Bar", "Baz").canonicalName()); | |
203 | assertEquals("a.b.c.Foo.Bar.Baz", ClassName.get("a.b.c", "Foo", "Bar", "Baz").canonicalName()); | |
204 | } | |
195 | 205 | } |
338 | 338 | CodeBlock joined = codeBlocks.stream().collect(CodeBlock.joining(" || ", "start {", "} end")); |
339 | 339 | assertThat(joined.toString()).isEqualTo("start {\"hello\" || world.World || need tacos} end"); |
340 | 340 | } |
341 | ||
342 | @Test public void clear() { | |
343 | CodeBlock block = CodeBlock.builder() | |
344 | .addStatement("$S", "Test string") | |
345 | .clear() | |
346 | .build(); | |
347 | ||
348 | assertThat(block.toString()).isEmpty(); | |
349 | } | |
341 | 350 | } |
0 | package com.squareup.javapoet; | |
1 | ||
2 | import org.junit.Test; | |
3 | ||
4 | import java.io.IOException; | |
5 | ||
6 | import static com.google.common.truth.Truth.assertThat; | |
7 | ||
8 | public class CodeWriterTest { | |
9 | ||
10 | @Test | |
11 | public void emptyLineInJavaDocDosEndings() throws IOException { | |
12 | CodeBlock javadocCodeBlock = CodeBlock.of("A\r\n\r\nB\r\n"); | |
13 | StringBuilder out = new StringBuilder(); | |
14 | new CodeWriter(out).emitJavadoc(javadocCodeBlock); | |
15 | assertThat(out.toString()).isEqualTo( | |
16 | "/**\n" + | |
17 | " * A\n" + | |
18 | " *\n" + | |
19 | " * B\n" + | |
20 | " */\n"); | |
21 | } | |
22 | }⏎ |
27 | 27 | FieldSpec b = FieldSpec.builder(int.class, "foo").build(); |
28 | 28 | assertThat(a.equals(b)).isTrue(); |
29 | 29 | assertThat(a.hashCode()).isEqualTo(b.hashCode()); |
30 | assertThat(a.toString()).isEqualTo(b.toString()); | |
30 | 31 | a = FieldSpec.builder(int.class, "FOO", Modifier.PUBLIC, Modifier.STATIC).build(); |
31 | 32 | b = FieldSpec.builder(int.class, "FOO", Modifier.PUBLIC, Modifier.STATIC).build(); |
32 | 33 | assertThat(a.equals(b)).isTrue(); |
33 | 34 | assertThat(a.hashCode()).isEqualTo(b.hashCode()); |
35 | assertThat(a.toString()).isEqualTo(b.toString()); | |
34 | 36 | } |
35 | 37 | |
36 | 38 | @Test public void nullAnnotationsAddition() { |
43 | 45 | .isEqualTo("annotationSpecs == null"); |
44 | 46 | } |
45 | 47 | } |
46 | }⏎ | |
48 | ||
49 | @Test public void modifyAnnotations() { | |
50 | FieldSpec.Builder builder = FieldSpec.builder(int.class, "foo") | |
51 | .addAnnotation(Override.class) | |
52 | .addAnnotation(SuppressWarnings.class); | |
53 | ||
54 | builder.annotations.remove(1); | |
55 | assertThat(builder.build().annotations).hasSize(1); | |
56 | } | |
57 | ||
58 | @Test public void modifyModifiers() { | |
59 | FieldSpec.Builder builder = FieldSpec.builder(int.class, "foo") | |
60 | .addModifiers(Modifier.PUBLIC, Modifier.STATIC); | |
61 | ||
62 | builder.modifiers.remove(1); | |
63 | assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC); | |
64 | } | |
65 | } |
215 | 215 | + "class Taco {\n" |
216 | 216 | + "}\n"); |
217 | 217 | } |
218 | ||
219 | @Test public void writeToPathReturnsPath() throws IOException { | |
220 | JavaFile javaFile = JavaFile.builder("foo", TypeSpec.classBuilder("Taco").build()).build(); | |
221 | Path filePath = javaFile.writeToPath(fsRoot); | |
222 | // Cast to avoid ambiguity between assertThat(Path) and assertThat(Iterable<?>) | |
223 | assertThat((Iterable<?>) filePath).isEqualTo(fsRoot.resolve(fs.getPath("foo", "Taco.java"))); | |
224 | } | |
218 | 225 | } |
14 | 14 | */ |
15 | 15 | package com.squareup.javapoet; |
16 | 16 | |
17 | import java.io.File; | |
18 | import com.google.testing.compile.CompilationRule; | |
17 | 19 | import java.util.Collections; |
18 | 20 | import java.util.Date; |
19 | 21 | import java.util.List; |
22 | import java.util.Map; | |
23 | import java.util.Optional; | |
20 | 24 | import java.util.concurrent.TimeUnit; |
25 | import java.util.regex.Pattern; | |
21 | 26 | import javax.lang.model.element.Modifier; |
27 | import javax.lang.model.element.TypeElement; | |
22 | 28 | import org.junit.Ignore; |
29 | import org.junit.Rule; | |
23 | 30 | import org.junit.Test; |
24 | 31 | import org.junit.runner.RunWith; |
25 | 32 | import org.junit.runners.JUnit4; |
28 | 35 | |
29 | 36 | @RunWith(JUnit4.class) |
30 | 37 | public final class JavaFileTest { |
38 | ||
39 | @Rule public final CompilationRule compilation = new CompilationRule(); | |
40 | ||
41 | private TypeElement getElement(Class<?> clazz) { | |
42 | return compilation.getElements().getTypeElement(clazz.getCanonicalName()); | |
43 | } | |
44 | ||
31 | 45 | @Test public void importStaticReadmeExample() { |
32 | 46 | ClassName hoverboard = ClassName.get("com.mattel", "Hoverboard"); |
33 | 47 | ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards"); |
688 | 702 | + " A a;\n" |
689 | 703 | + "}\n"); |
690 | 704 | } |
705 | ||
706 | @Test public void modifyStaticImports() throws Exception { | |
707 | JavaFile.Builder builder = JavaFile.builder("com.squareup.tacos", | |
708 | TypeSpec.classBuilder("Taco") | |
709 | .build()) | |
710 | .addStaticImport(File.class, "separator"); | |
711 | ||
712 | builder.staticImports.clear(); | |
713 | builder.staticImports.add(File.class.getCanonicalName() + ".separatorChar"); | |
714 | ||
715 | String source = builder.build().toString(); | |
716 | ||
717 | assertThat(source).isEqualTo("" | |
718 | + "package com.squareup.tacos;\n" | |
719 | + "\n" | |
720 | + "import static java.io.File.separatorChar;\n" | |
721 | + "\n" | |
722 | + "class Taco {\n" | |
723 | + "}\n"); | |
724 | } | |
725 | ||
726 | @Test public void alwaysQualifySimple() { | |
727 | String source = JavaFile.builder("com.squareup.tacos", | |
728 | TypeSpec.classBuilder("Taco") | |
729 | .addField(Thread.class, "thread") | |
730 | .alwaysQualify("Thread") | |
731 | .build()) | |
732 | .build() | |
733 | .toString(); | |
734 | assertThat(source).isEqualTo("" | |
735 | + "package com.squareup.tacos;\n" | |
736 | + "\n" | |
737 | + "class Taco {\n" | |
738 | + " java.lang.Thread thread;\n" | |
739 | + "}\n"); | |
740 | } | |
741 | ||
742 | @Test public void alwaysQualifySupersedesJavaLangImports() { | |
743 | String source = JavaFile.builder("com.squareup.tacos", | |
744 | TypeSpec.classBuilder("Taco") | |
745 | .addField(Thread.class, "thread") | |
746 | .alwaysQualify("Thread") | |
747 | .build()) | |
748 | .skipJavaLangImports(true) | |
749 | .build() | |
750 | .toString(); | |
751 | assertThat(source).isEqualTo("" | |
752 | + "package com.squareup.tacos;\n" | |
753 | + "\n" | |
754 | + "class Taco {\n" | |
755 | + " java.lang.Thread thread;\n" | |
756 | + "}\n"); | |
757 | } | |
758 | ||
759 | @Test public void avoidClashesWithNestedClasses_viaClass() { | |
760 | String source = JavaFile.builder("com.squareup.tacos", | |
761 | TypeSpec.classBuilder("Taco") | |
762 | // These two should get qualified | |
763 | .addField(ClassName.get("other", "NestedTypeA"), "nestedA") | |
764 | .addField(ClassName.get("other", "NestedTypeB"), "nestedB") | |
765 | // This one shouldn't since it's not a nested type of Foo | |
766 | .addField(ClassName.get("other", "NestedTypeC"), "nestedC") | |
767 | // This one shouldn't since we only look at nested types | |
768 | .addField(ClassName.get("other", "Foo"), "foo") | |
769 | .avoidClashesWithNestedClasses(Foo.class) | |
770 | .build()) | |
771 | .build() | |
772 | .toString(); | |
773 | assertThat(source).isEqualTo("" | |
774 | + "package com.squareup.tacos;\n" | |
775 | + "\n" | |
776 | + "import other.Foo;\n" | |
777 | + "import other.NestedTypeC;\n" | |
778 | + "\n" | |
779 | + "class Taco {\n" | |
780 | + " other.NestedTypeA nestedA;\n" | |
781 | + "\n" | |
782 | + " other.NestedTypeB nestedB;\n" | |
783 | + "\n" | |
784 | + " NestedTypeC nestedC;\n" | |
785 | + "\n" | |
786 | + " Foo foo;\n" | |
787 | + "}\n"); | |
788 | } | |
789 | ||
790 | @Test public void avoidClashesWithNestedClasses_viaTypeElement() { | |
791 | String source = JavaFile.builder("com.squareup.tacos", | |
792 | TypeSpec.classBuilder("Taco") | |
793 | // These two should get qualified | |
794 | .addField(ClassName.get("other", "NestedTypeA"), "nestedA") | |
795 | .addField(ClassName.get("other", "NestedTypeB"), "nestedB") | |
796 | // This one shouldn't since it's not a nested type of Foo | |
797 | .addField(ClassName.get("other", "NestedTypeC"), "nestedC") | |
798 | // This one shouldn't since we only look at nested types | |
799 | .addField(ClassName.get("other", "Foo"), "foo") | |
800 | .avoidClashesWithNestedClasses(getElement(Foo.class)) | |
801 | .build()) | |
802 | .build() | |
803 | .toString(); | |
804 | assertThat(source).isEqualTo("" | |
805 | + "package com.squareup.tacos;\n" | |
806 | + "\n" | |
807 | + "import other.Foo;\n" | |
808 | + "import other.NestedTypeC;\n" | |
809 | + "\n" | |
810 | + "class Taco {\n" | |
811 | + " other.NestedTypeA nestedA;\n" | |
812 | + "\n" | |
813 | + " other.NestedTypeB nestedB;\n" | |
814 | + "\n" | |
815 | + " NestedTypeC nestedC;\n" | |
816 | + "\n" | |
817 | + " Foo foo;\n" | |
818 | + "}\n"); | |
819 | } | |
820 | ||
821 | @Test public void avoidClashesWithNestedClasses_viaSuperinterfaceType() { | |
822 | String source = JavaFile.builder("com.squareup.tacos", | |
823 | TypeSpec.classBuilder("Taco") | |
824 | // These two should get qualified | |
825 | .addField(ClassName.get("other", "NestedTypeA"), "nestedA") | |
826 | .addField(ClassName.get("other", "NestedTypeB"), "nestedB") | |
827 | // This one shouldn't since it's not a nested type of Foo | |
828 | .addField(ClassName.get("other", "NestedTypeC"), "nestedC") | |
829 | // This one shouldn't since we only look at nested types | |
830 | .addField(ClassName.get("other", "Foo"), "foo") | |
831 | .addType(TypeSpec.classBuilder("NestedTypeA").build()) | |
832 | .addType(TypeSpec.classBuilder("NestedTypeB").build()) | |
833 | .addSuperinterface(FooInterface.class) | |
834 | .build()) | |
835 | .build() | |
836 | .toString(); | |
837 | assertThat(source).isEqualTo("package com.squareup.tacos;\n" | |
838 | + "\n" | |
839 | + "import com.squareup.javapoet.JavaFileTest;\n" | |
840 | + "import other.Foo;\n" | |
841 | + "import other.NestedTypeC;\n" | |
842 | + "\n" | |
843 | + "class Taco implements JavaFileTest.FooInterface {\n" | |
844 | + " other.NestedTypeA nestedA;\n" | |
845 | + "\n" | |
846 | + " other.NestedTypeB nestedB;\n" | |
847 | + "\n" | |
848 | + " NestedTypeC nestedC;\n" | |
849 | + "\n" | |
850 | + " Foo foo;\n" | |
851 | + "\n" | |
852 | + " class NestedTypeA {\n" | |
853 | + " }\n" | |
854 | + "\n" | |
855 | + " class NestedTypeB {\n" | |
856 | + " }\n" | |
857 | + "}\n"); | |
858 | } | |
859 | ||
860 | static class Foo { | |
861 | static class NestedTypeA { | |
862 | ||
863 | } | |
864 | static class NestedTypeB { | |
865 | ||
866 | } | |
867 | } | |
868 | ||
869 | interface FooInterface { | |
870 | class NestedTypeA { | |
871 | ||
872 | } | |
873 | class NestedTypeB { | |
874 | ||
875 | } | |
876 | } | |
877 | ||
878 | private TypeSpec.Builder childTypeBuilder() { | |
879 | return TypeSpec.classBuilder("Child") | |
880 | .addMethod(MethodSpec.methodBuilder("optionalString") | |
881 | .returns(ParameterizedTypeName.get(Optional.class, String.class)) | |
882 | .addStatement("return $T.empty()", Optional.class) | |
883 | .build()) | |
884 | .addMethod(MethodSpec.methodBuilder("pattern") | |
885 | .returns(Pattern.class) | |
886 | .addStatement("return null") | |
887 | .build()); | |
888 | } | |
889 | ||
890 | @Test | |
891 | public void avoidClashes_parentChild_superclass_type() { | |
892 | String source = JavaFile.builder("com.squareup.javapoet", | |
893 | childTypeBuilder().superclass(Parent.class).build()) | |
894 | .build() | |
895 | .toString(); | |
896 | assertThat(source).isEqualTo("package com.squareup.javapoet;\n" | |
897 | + "\n" | |
898 | + "import java.lang.String;\n" | |
899 | + "\n" | |
900 | + "class Child extends JavaFileTest.Parent {\n" | |
901 | + " java.util.Optional<String> optionalString() {\n" | |
902 | + " return java.util.Optional.empty();\n" | |
903 | + " }\n" | |
904 | + "\n" | |
905 | + " java.util.regex.Pattern pattern() {\n" | |
906 | + " return null;\n" | |
907 | + " }\n" | |
908 | + "}\n"); | |
909 | } | |
910 | ||
911 | @Test | |
912 | public void avoidClashes_parentChild_superclass_typeMirror() { | |
913 | String source = JavaFile.builder("com.squareup.javapoet", | |
914 | childTypeBuilder().superclass(getElement(Parent.class).asType()).build()) | |
915 | .build() | |
916 | .toString(); | |
917 | assertThat(source).isEqualTo("package com.squareup.javapoet;\n" | |
918 | + "\n" | |
919 | + "import java.lang.String;\n" | |
920 | + "\n" | |
921 | + "class Child extends JavaFileTest.Parent {\n" | |
922 | + " java.util.Optional<String> optionalString() {\n" | |
923 | + " return java.util.Optional.empty();\n" | |
924 | + " }\n" | |
925 | + "\n" | |
926 | + " java.util.regex.Pattern pattern() {\n" | |
927 | + " return null;\n" | |
928 | + " }\n" | |
929 | + "}\n"); | |
930 | } | |
931 | ||
932 | @Test | |
933 | public void avoidClashes_parentChild_superinterface_type() { | |
934 | String source = JavaFile.builder("com.squareup.javapoet", | |
935 | childTypeBuilder().addSuperinterface(ParentInterface.class).build()) | |
936 | .build() | |
937 | .toString(); | |
938 | assertThat(source).isEqualTo("package com.squareup.javapoet;\n" | |
939 | + "\n" | |
940 | + "import java.lang.String;\n" | |
941 | + "import java.util.regex.Pattern;\n" | |
942 | + "\n" | |
943 | + "class Child implements JavaFileTest.ParentInterface {\n" | |
944 | + " java.util.Optional<String> optionalString() {\n" | |
945 | + " return java.util.Optional.empty();\n" | |
946 | + " }\n" | |
947 | + "\n" | |
948 | + " Pattern pattern() {\n" | |
949 | + " return null;\n" | |
950 | + " }\n" | |
951 | + "}\n"); | |
952 | } | |
953 | ||
954 | @Test | |
955 | public void avoidClashes_parentChild_superinterface_typeMirror() { | |
956 | String source = JavaFile.builder("com.squareup.javapoet", | |
957 | childTypeBuilder().addSuperinterface(getElement(ParentInterface.class).asType()).build()) | |
958 | .build() | |
959 | .toString(); | |
960 | assertThat(source).isEqualTo("package com.squareup.javapoet;\n" | |
961 | + "\n" | |
962 | + "import java.lang.String;\n" | |
963 | + "import java.util.regex.Pattern;\n" | |
964 | + "\n" | |
965 | + "class Child implements JavaFileTest.ParentInterface {\n" | |
966 | + " java.util.Optional<String> optionalString() {\n" | |
967 | + " return java.util.Optional.empty();\n" | |
968 | + " }\n" | |
969 | + "\n" | |
970 | + " Pattern pattern() {\n" | |
971 | + " return null;\n" | |
972 | + " }\n" | |
973 | + "}\n"); | |
974 | } | |
975 | ||
976 | // Regression test for https://github.com/square/javapoet/issues/77 | |
977 | // This covers class and inheritance | |
978 | static class Parent implements ParentInterface { | |
979 | static class Pattern { | |
980 | ||
981 | } | |
982 | } | |
983 | ||
984 | interface ParentInterface { | |
985 | class Optional { | |
986 | ||
987 | } | |
988 | } | |
989 | ||
990 | // Regression test for case raised here: https://github.com/square/javapoet/issues/77#issuecomment-519972404 | |
991 | @Test | |
992 | public void avoidClashes_mapEntry() { | |
993 | String source = JavaFile.builder("com.squareup.javapoet", | |
994 | TypeSpec.classBuilder("MapType") | |
995 | .addMethod(MethodSpec.methodBuilder("optionalString") | |
996 | .returns(ClassName.get("com.foo", "Entry")) | |
997 | .addStatement("return null") | |
998 | .build()) | |
999 | .addSuperinterface(Map.class) | |
1000 | .build()) | |
1001 | .build() | |
1002 | .toString(); | |
1003 | assertThat(source).isEqualTo("package com.squareup.javapoet;\n" | |
1004 | + "\n" | |
1005 | + "import java.util.Map;\n" | |
1006 | + "\n" | |
1007 | + "class MapType implements Map {\n" | |
1008 | + " com.foo.Entry optionalString() {\n" | |
1009 | + " return null;\n" | |
1010 | + " }\n" | |
1011 | + "}\n"); | |
1012 | } | |
691 | 1013 | } |
20 | 20 | import java.lang.annotation.ElementType; |
21 | 21 | import java.lang.annotation.Target; |
22 | 22 | import java.util.Arrays; |
23 | import java.util.Collection; | |
23 | import java.util.HashMap; | |
24 | 24 | import java.util.List; |
25 | import java.util.Map; | |
25 | 26 | import java.util.concurrent.Callable; |
26 | 27 | import java.util.concurrent.TimeoutException; |
27 | 28 | import javax.lang.model.element.ExecutableElement; |
36 | 37 | |
37 | 38 | import static com.google.common.collect.Iterables.getOnlyElement; |
38 | 39 | import static com.google.common.truth.Truth.assertThat; |
40 | import static com.squareup.javapoet.MethodSpec.CONSTRUCTOR; | |
41 | import static com.squareup.javapoet.TestUtil.findFirst; | |
39 | 42 | import static javax.lang.model.util.ElementFilter.methodsIn; |
40 | 43 | import static org.junit.Assert.fail; |
41 | 44 | |
52 | 55 | |
53 | 56 | private TypeElement getElement(Class<?> clazz) { |
54 | 57 | return elements.getTypeElement(clazz.getCanonicalName()); |
55 | } | |
56 | ||
57 | private ExecutableElement findFirst(Collection<ExecutableElement> elements, String name) { | |
58 | for (ExecutableElement executableElement : elements) { | |
59 | if (executableElement.getSimpleName().toString().equals(name)) { | |
60 | return executableElement; | |
61 | } | |
62 | } | |
63 | throw new IllegalArgumentException(name + " not found in " + elements); | |
64 | 58 | } |
65 | 59 | |
66 | 60 | @Test public void nullAnnotationsAddition() { |
269 | 263 | assertThat(a.hashCode()).isEqualTo(b.hashCode()); |
270 | 264 | } |
271 | 265 | |
266 | @Test public void withoutParameterJavaDoc() { | |
267 | MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco") | |
268 | .addModifiers(Modifier.PRIVATE) | |
269 | .addParameter(TypeName.DOUBLE, "money") | |
270 | .addJavadoc("Gets the best Taco\n") | |
271 | .build(); | |
272 | assertThat(methodSpec.toString()).isEqualTo("" | |
273 | + "/**\n" | |
274 | + " * Gets the best Taco\n" | |
275 | + " */\n" | |
276 | + "private void getTaco(double money) {\n" | |
277 | + "}\n"); | |
278 | } | |
279 | ||
280 | @Test public void withParameterJavaDoc() { | |
281 | MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco") | |
282 | .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money") | |
283 | .addJavadoc("the amount required to buy the taco.\n") | |
284 | .build()) | |
285 | .addParameter(ParameterSpec.builder(TypeName.INT, "count") | |
286 | .addJavadoc("the number of Tacos to buy.\n") | |
287 | .build()) | |
288 | .addJavadoc("Gets the best Taco money can buy.\n") | |
289 | .build(); | |
290 | assertThat(methodSpec.toString()).isEqualTo("" | |
291 | + "/**\n" | |
292 | + " * Gets the best Taco money can buy.\n" | |
293 | + " *\n" | |
294 | + " * @param money the amount required to buy the taco.\n" | |
295 | + " * @param count the number of Tacos to buy.\n" | |
296 | + " */\n" | |
297 | + "void getTaco(double money, int count) {\n" | |
298 | + "}\n"); | |
299 | } | |
300 | ||
301 | @Test public void withParameterJavaDocAndWithoutMethodJavadoc() { | |
302 | MethodSpec methodSpec = MethodSpec.methodBuilder("getTaco") | |
303 | .addParameter(ParameterSpec.builder(TypeName.DOUBLE, "money") | |
304 | .addJavadoc("the amount required to buy the taco.\n") | |
305 | .build()) | |
306 | .addParameter(ParameterSpec.builder(TypeName.INT, "count") | |
307 | .addJavadoc("the number of Tacos to buy.\n") | |
308 | .build()) | |
309 | .build(); | |
310 | assertThat(methodSpec.toString()).isEqualTo("" | |
311 | + "/**\n" | |
312 | + " * @param money the amount required to buy the taco.\n" | |
313 | + " * @param count the number of Tacos to buy.\n" | |
314 | + " */\n" | |
315 | + "void getTaco(double money, int count) {\n" | |
316 | + "}\n"); | |
317 | } | |
318 | ||
272 | 319 | @Test public void duplicateExceptionsIgnored() { |
273 | 320 | ClassName ioException = ClassName.get(IOException.class); |
274 | 321 | ClassName timeoutException = ClassName.get(TimeoutException.class); |
301 | 348 | assertThat(e.getMessage()).isEqualTo("modifiers == null"); |
302 | 349 | } |
303 | 350 | } |
351 | ||
352 | @Test public void modifyMethodName() { | |
353 | MethodSpec methodSpec = MethodSpec.methodBuilder("initialMethod") | |
354 | .build() | |
355 | .toBuilder() | |
356 | .setName("revisedMethod") | |
357 | .build(); | |
358 | ||
359 | assertThat(methodSpec.toString()).isEqualTo("" + "void revisedMethod() {\n" + "}\n"); | |
360 | } | |
361 | ||
362 | @Test public void modifyAnnotations() { | |
363 | MethodSpec.Builder builder = MethodSpec.methodBuilder("foo") | |
364 | .addAnnotation(Override.class) | |
365 | .addAnnotation(SuppressWarnings.class); | |
366 | ||
367 | builder.annotations.remove(1); | |
368 | assertThat(builder.build().annotations).hasSize(1); | |
369 | } | |
370 | ||
371 | @Test public void modifyModifiers() { | |
372 | MethodSpec.Builder builder = MethodSpec.methodBuilder("foo") | |
373 | .addModifiers(Modifier.PUBLIC, Modifier.STATIC); | |
374 | ||
375 | builder.modifiers.remove(1); | |
376 | assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC); | |
377 | } | |
378 | ||
379 | @Test public void modifyParameters() { | |
380 | MethodSpec.Builder builder = MethodSpec.methodBuilder("foo") | |
381 | .addParameter(int.class, "source"); | |
382 | ||
383 | builder.parameters.remove(0); | |
384 | assertThat(builder.build().parameters).isEmpty(); | |
385 | } | |
386 | ||
387 | @Test public void modifyTypeVariables() { | |
388 | TypeVariableName t = TypeVariableName.get("T"); | |
389 | MethodSpec.Builder builder = MethodSpec.methodBuilder("foo") | |
390 | .addTypeVariable(t) | |
391 | .addTypeVariable(TypeVariableName.get("V")); | |
392 | ||
393 | builder.typeVariables.remove(1); | |
394 | assertThat(builder.build().typeVariables).containsExactly(t); | |
395 | } | |
396 | ||
397 | @Test public void ensureTrailingNewline() { | |
398 | MethodSpec methodSpec = MethodSpec.methodBuilder("method") | |
399 | .addCode("codeWithNoNewline();") | |
400 | .build(); | |
401 | ||
402 | assertThat(methodSpec.toString()).isEqualTo("" | |
403 | + "void method() {\n" | |
404 | + " codeWithNoNewline();\n" | |
405 | + "}\n"); | |
406 | } | |
407 | ||
408 | /** Ensures that we don't add a duplicate newline if one is already present. */ | |
409 | @Test public void ensureTrailingNewlineWithExistingNewline() { | |
410 | MethodSpec methodSpec = MethodSpec.methodBuilder("method") | |
411 | .addCode("codeWithNoNewline();\n") // Have a newline already, so ensure we're not adding one | |
412 | .build(); | |
413 | ||
414 | assertThat(methodSpec.toString()).isEqualTo("" | |
415 | + "void method() {\n" | |
416 | + " codeWithNoNewline();\n" | |
417 | + "}\n"); | |
418 | } | |
419 | ||
420 | @Test public void controlFlowWithNamedCodeBlocks() { | |
421 | Map<String, Object> m = new HashMap<>(); | |
422 | m.put("field", "valueField"); | |
423 | m.put("threshold", "5"); | |
424 | ||
425 | MethodSpec methodSpec = MethodSpec.methodBuilder("method") | |
426 | .beginControlFlow(named("if ($field:N > $threshold:L)", m)) | |
427 | .nextControlFlow(named("else if ($field:N == $threshold:L)", m)) | |
428 | .endControlFlow() | |
429 | .build(); | |
430 | ||
431 | assertThat(methodSpec.toString()).isEqualTo("" | |
432 | + "void method() {\n" | |
433 | + " if (valueField > 5) {\n" | |
434 | + " } else if (valueField == 5) {\n" | |
435 | + " }\n" | |
436 | + "}\n"); | |
437 | } | |
438 | ||
439 | @Test public void doWhileWithNamedCodeBlocks() { | |
440 | Map<String, Object> m = new HashMap<>(); | |
441 | m.put("field", "valueField"); | |
442 | m.put("threshold", "5"); | |
443 | ||
444 | MethodSpec methodSpec = MethodSpec.methodBuilder("method") | |
445 | .beginControlFlow("do") | |
446 | .addStatement(named("$field:N--", m)) | |
447 | .endControlFlow(named("while ($field:N > $threshold:L)", m)) | |
448 | .build(); | |
449 | ||
450 | assertThat(methodSpec.toString()).isEqualTo("" | |
451 | + "void method() {\n" + | |
452 | " do {\n" + | |
453 | " valueField--;\n" + | |
454 | " } while (valueField > 5);\n" + | |
455 | "}\n"); | |
456 | } | |
457 | ||
458 | private static CodeBlock named(String format, Map<String, ?> args){ | |
459 | return CodeBlock.builder().addNamed(format, args).build(); | |
460 | } | |
461 | ||
304 | 462 | } |
17 | 17 | import org.junit.Test; |
18 | 18 | |
19 | 19 | import static com.google.common.truth.Truth.assertThat; |
20 | import static org.junit.Assert.assertEquals; | |
20 | 21 | import static org.junit.Assert.fail; |
21 | 22 | |
22 | 23 | public final class NameAllocatorTest { |
24 | ||
23 | 25 | @Test public void usage() throws Exception { |
24 | 26 | NameAllocator nameAllocator = new NameAllocator(); |
25 | 27 | assertThat(nameAllocator.newName("foo", 1)).isEqualTo("foo"); |
58 | 60 | @Test public void characterMappingInvalidStartButValidPart() throws Exception { |
59 | 61 | NameAllocator nameAllocator = new NameAllocator(); |
60 | 62 | assertThat(nameAllocator.newName("1ab", 1)).isEqualTo("_1ab"); |
63 | assertThat(nameAllocator.newName("a-1", 2)).isEqualTo("a_1"); | |
61 | 64 | } |
62 | 65 | |
63 | 66 | @Test public void characterMappingInvalidStartIsInvalidPart() throws Exception { |
14 | 14 | */ |
15 | 15 | package com.squareup.javapoet; |
16 | 16 | |
17 | import com.google.testing.compile.CompilationRule; | |
18 | import java.util.ArrayList; | |
19 | import java.util.List; | |
20 | import javax.annotation.Nullable; | |
21 | import javax.lang.model.element.ExecutableElement; | |
22 | import javax.lang.model.element.TypeElement; | |
23 | import javax.lang.model.element.VariableElement; | |
24 | import javax.lang.model.util.Elements; | |
25 | import org.junit.Before; | |
26 | import org.junit.Rule; | |
27 | import javax.lang.model.element.Modifier; | |
17 | 28 | import org.junit.Test; |
18 | 29 | |
19 | 30 | import static com.google.common.truth.Truth.assertThat; |
31 | import static com.squareup.javapoet.TestUtil.findFirst; | |
32 | import static javax.lang.model.util.ElementFilter.fieldsIn; | |
33 | import static javax.lang.model.util.ElementFilter.methodsIn; | |
20 | 34 | import static org.junit.Assert.fail; |
21 | 35 | |
22 | import javax.lang.model.element.Modifier; | |
36 | public class ParameterSpecTest { | |
37 | @Rule public final CompilationRule compilation = new CompilationRule(); | |
23 | 38 | |
24 | public class ParameterSpecTest { | |
39 | private Elements elements; | |
40 | ||
41 | @Before public void setUp() { | |
42 | elements = compilation.getElements(); | |
43 | } | |
44 | ||
45 | private TypeElement getElement(Class<?> clazz) { | |
46 | return elements.getTypeElement(clazz.getCanonicalName()); | |
47 | } | |
48 | ||
25 | 49 | @Test public void equalsAndHashCode() { |
26 | 50 | ParameterSpec a = ParameterSpec.builder(int.class, "foo").build(); |
27 | 51 | ParameterSpec b = ParameterSpec.builder(int.class, "foo").build(); |
28 | 52 | assertThat(a.equals(b)).isTrue(); |
29 | 53 | assertThat(a.hashCode()).isEqualTo(b.hashCode()); |
54 | assertThat(a.toString()).isEqualTo(b.toString()); | |
30 | 55 | a = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build(); |
31 | 56 | b = ParameterSpec.builder(int.class, "i").addModifiers(Modifier.STATIC).build(); |
32 | 57 | assertThat(a.equals(b)).isTrue(); |
33 | 58 | assertThat(a.hashCode()).isEqualTo(b.hashCode()); |
59 | assertThat(a.toString()).isEqualTo(b.toString()); | |
34 | 60 | } |
35 | 61 | |
36 | 62 | @Test public void nullAnnotationsAddition() { |
42 | 68 | .isEqualTo("annotationSpecs == null"); |
43 | 69 | } |
44 | 70 | } |
45 | }⏎ | |
71 | ||
72 | final class VariableElementFieldClass { | |
73 | String name; | |
74 | } | |
75 | ||
76 | @Test public void fieldVariableElement() { | |
77 | TypeElement classElement = getElement(VariableElementFieldClass.class); | |
78 | List<VariableElement> methods = fieldsIn(elements.getAllMembers(classElement)); | |
79 | VariableElement element = findFirst(methods, "name"); | |
80 | ||
81 | try { | |
82 | ParameterSpec.get(element); | |
83 | fail(); | |
84 | } catch (IllegalArgumentException exception) { | |
85 | assertThat(exception).hasMessageThat().isEqualTo("element is not a parameter"); | |
86 | } | |
87 | } | |
88 | ||
89 | final class VariableElementParameterClass { | |
90 | public void foo(@Nullable final String bar) { | |
91 | } | |
92 | } | |
93 | ||
94 | @Test public void parameterVariableElement() { | |
95 | TypeElement classElement = getElement(VariableElementParameterClass.class); | |
96 | List<ExecutableElement> methods = methodsIn(elements.getAllMembers(classElement)); | |
97 | ExecutableElement element = findFirst(methods, "foo"); | |
98 | VariableElement parameterElement = element.getParameters().get(0); | |
99 | ||
100 | assertThat(ParameterSpec.get(parameterElement).toString()) | |
101 | .isEqualTo("@javax.annotation.Nullable java.lang.String arg0"); | |
102 | } | |
103 | ||
104 | @Test public void addNonFinalModifier() { | |
105 | List<Modifier> modifiers = new ArrayList<>(); | |
106 | modifiers.add(Modifier.FINAL); | |
107 | modifiers.add(Modifier.PUBLIC); | |
108 | ||
109 | try { | |
110 | ParameterSpec.builder(int.class, "foo") | |
111 | .addModifiers(modifiers); | |
112 | fail(); | |
113 | } catch (Exception e) { | |
114 | assertThat(e.getMessage()).isEqualTo("unexpected parameter modifier: public"); | |
115 | } | |
116 | } | |
117 | ||
118 | @Test public void modifyAnnotations() { | |
119 | ParameterSpec.Builder builder = ParameterSpec.builder(int.class, "foo") | |
120 | .addAnnotation(Override.class) | |
121 | .addAnnotation(SuppressWarnings.class); | |
122 | ||
123 | builder.annotations.remove(1); | |
124 | assertThat(builder.build().annotations).hasSize(1); | |
125 | } | |
126 | ||
127 | @Test public void modifyModifiers() { | |
128 | ParameterSpec.Builder builder = ParameterSpec.builder(int.class, "foo") | |
129 | .addModifiers(Modifier.PUBLIC, Modifier.STATIC); | |
130 | ||
131 | builder.modifiers.remove(1); | |
132 | assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC); | |
133 | } | |
134 | } |
0 | package com.squareup.javapoet; | |
1 | ||
2 | import javax.lang.model.element.Element; | |
3 | import javax.lang.model.element.ExecutableElement; | |
4 | import javax.lang.model.element.VariableElement; | |
5 | import java.util.Collection; | |
6 | ||
7 | final class TestUtil { | |
8 | static <E extends Element> E findFirst(Collection<E> elements, String name) { | |
9 | for (E element : elements) { | |
10 | if (element.getSimpleName().toString().equals(name)) { | |
11 | return element; | |
12 | } | |
13 | } | |
14 | throw new IllegalArgumentException(name + " not found in " + elements); | |
15 | } | |
16 | } |
16 | 16 | |
17 | 17 | import com.google.common.collect.ImmutableMap; |
18 | 18 | import com.google.testing.compile.CompilationRule; |
19 | import java.io.File; | |
19 | 20 | import java.io.IOException; |
20 | 21 | import java.io.Serializable; |
21 | 22 | import java.math.BigDecimal; |
984 | 985 | + "}\n"); |
985 | 986 | } |
986 | 987 | |
988 | @Test public void simpleNameConflictsWithTypeVariable() { | |
989 | ClassName inPackage = ClassName.get("com.squareup.tacos", "InPackage"); | |
990 | ClassName otherType = ClassName.get("com.other", "OtherType"); | |
991 | ClassName methodInPackage = ClassName.get("com.squareup.tacos", "MethodInPackage"); | |
992 | ClassName methodOtherType = ClassName.get("com.other", "MethodOtherType"); | |
993 | TypeSpec gen = TypeSpec.classBuilder("Gen") | |
994 | .addTypeVariable(TypeVariableName.get("InPackage")) | |
995 | .addTypeVariable(TypeVariableName.get("OtherType")) | |
996 | .addField(FieldSpec.builder(inPackage, "inPackage").build()) | |
997 | .addField(FieldSpec.builder(otherType, "otherType").build()) | |
998 | .addMethod(MethodSpec.methodBuilder("withTypeVariables") | |
999 | .addTypeVariable(TypeVariableName.get("MethodInPackage")) | |
1000 | .addTypeVariable(TypeVariableName.get("MethodOtherType")) | |
1001 | .addStatement("$T inPackage = null", methodInPackage) | |
1002 | .addStatement("$T otherType = null", methodOtherType) | |
1003 | .build()) | |
1004 | .addMethod(MethodSpec.methodBuilder("withoutTypeVariables") | |
1005 | .addStatement("$T inPackage = null", methodInPackage) | |
1006 | .addStatement("$T otherType = null", methodOtherType) | |
1007 | .build()) | |
1008 | .addMethod(MethodSpec.methodBuilder("againWithTypeVariables") | |
1009 | .addTypeVariable(TypeVariableName.get("MethodInPackage")) | |
1010 | .addTypeVariable(TypeVariableName.get("MethodOtherType")) | |
1011 | .addStatement("$T inPackage = null", methodInPackage) | |
1012 | .addStatement("$T otherType = null", methodOtherType) | |
1013 | .build()) | |
1014 | // https://github.com/square/javapoet/pull/657#discussion_r205514292 | |
1015 | .addMethod(MethodSpec.methodBuilder("masksEnclosingTypeVariable") | |
1016 | .addTypeVariable(TypeVariableName.get("InPackage")) | |
1017 | .build()) | |
1018 | .addMethod(MethodSpec.methodBuilder("hasSimpleNameThatWasPreviouslyMasked") | |
1019 | .addStatement("$T inPackage = null", inPackage) | |
1020 | .build()) | |
1021 | .build(); | |
1022 | assertThat(toString(gen)).isEqualTo("" | |
1023 | + "package com.squareup.tacos;\n" | |
1024 | + "\n" | |
1025 | + "import com.other.MethodOtherType;\n" | |
1026 | + "\n" | |
1027 | + "class Gen<InPackage, OtherType> {\n" | |
1028 | + " com.squareup.tacos.InPackage inPackage;\n" | |
1029 | + "\n" | |
1030 | + " com.other.OtherType otherType;\n" | |
1031 | + "\n" | |
1032 | + " <MethodInPackage, MethodOtherType> void withTypeVariables() {\n" | |
1033 | + " com.squareup.tacos.MethodInPackage inPackage = null;\n" | |
1034 | + " com.other.MethodOtherType otherType = null;\n" | |
1035 | + " }\n" | |
1036 | + "\n" | |
1037 | + " void withoutTypeVariables() {\n" | |
1038 | + " MethodInPackage inPackage = null;\n" | |
1039 | + " MethodOtherType otherType = null;\n" | |
1040 | + " }\n" | |
1041 | + "\n" | |
1042 | + " <MethodInPackage, MethodOtherType> void againWithTypeVariables() {\n" | |
1043 | + " com.squareup.tacos.MethodInPackage inPackage = null;\n" | |
1044 | + " com.other.MethodOtherType otherType = null;\n" | |
1045 | + " }\n" | |
1046 | + "\n" | |
1047 | + " <InPackage> void masksEnclosingTypeVariable() {\n" | |
1048 | + " }\n" | |
1049 | + "\n" | |
1050 | + " void hasSimpleNameThatWasPreviouslyMasked() {\n" | |
1051 | + " com.squareup.tacos.InPackage inPackage = null;\n" | |
1052 | + " }\n" | |
1053 | + "}\n"); | |
1054 | } | |
1055 | ||
987 | 1056 | @Test public void originatingElementsIncludesThoseOfNestedTypes() { |
988 | 1057 | Element outerElement = Mockito.mock(Element.class); |
989 | 1058 | Element innerElement = Mockito.mock(Element.class); |
1779 | 1848 | + " }\n" |
1780 | 1849 | + "\n" |
1781 | 1850 | + " /**\n" |
1782 | + " * chosen by fair dice roll ;) */\n" | |
1851 | + " * chosen by fair dice roll ;)\n" | |
1852 | + " */\n" | |
1783 | 1853 | + " public int getRandomQuantity() {\n" |
1784 | 1854 | + " return 4;\n" |
1785 | 1855 | + " }\n" |
1838 | 1908 | |
1839 | 1909 | @Test public void nullModifiersAddition() { |
1840 | 1910 | try { |
1841 | TypeSpec.classBuilder("Taco").addModifiers((Modifier) null); | |
1911 | TypeSpec.classBuilder("Taco").addModifiers((Modifier) null).build(); | |
1842 | 1912 | fail(); |
1843 | 1913 | } catch(IllegalArgumentException expected) { |
1844 | 1914 | assertThat(expected.getMessage()) |
2195 | 2265 | |
2196 | 2266 | @Test public void initializersToBuilder() { |
2197 | 2267 | // Tests if toBuilder() contains correct static and instance initializers |
2268 | Element originatingElement = getElement(TypeSpecTest.class); | |
2198 | 2269 | TypeSpec taco = TypeSpec.classBuilder("Taco") |
2199 | 2270 | .addField(String.class, "foo", Modifier.PRIVATE) |
2200 | 2271 | .addField(String.class, "FOO", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL) |
2211 | 2282 | .addInitializerBlock(CodeBlock.builder() |
2212 | 2283 | .addStatement("foo = $S", "FOO") |
2213 | 2284 | .build()) |
2285 | .addOriginatingElement(originatingElement) | |
2286 | .alwaysQualify("com.example.AlwaysQualified") | |
2214 | 2287 | .build(); |
2215 | 2288 | |
2216 | 2289 | TypeSpec recreatedTaco = taco.toBuilder().build(); |
2217 | 2290 | assertThat(toString(taco)).isEqualTo(toString(recreatedTaco)); |
2291 | assertThat(taco.originatingElements) | |
2292 | .containsExactlyElementsIn(recreatedTaco.originatingElements); | |
2293 | assertThat(taco.alwaysQualifiedNames) | |
2294 | .containsExactlyElementsIn(recreatedTaco.alwaysQualifiedNames); | |
2218 | 2295 | |
2219 | 2296 | TypeSpec initializersAdded = taco.toBuilder() |
2220 | 2297 | .addInitializerBlock(CodeBlock.builder() |
2355 | 2432 | assertThat(TypeSpec.enumBuilder(className).addEnumConstant("A").build().name).isEqualTo("Example"); |
2356 | 2433 | assertThat(TypeSpec.annotationBuilder(className).build().name).isEqualTo("Example"); |
2357 | 2434 | } |
2435 | ||
2436 | @Test | |
2437 | public void modifyAnnotations() { | |
2438 | TypeSpec.Builder builder = | |
2439 | TypeSpec.classBuilder("Taco") | |
2440 | .addAnnotation(Override.class) | |
2441 | .addAnnotation(SuppressWarnings.class); | |
2442 | ||
2443 | builder.annotations.remove(1); | |
2444 | assertThat(builder.build().annotations).hasSize(1); | |
2445 | } | |
2446 | ||
2447 | @Test | |
2448 | public void modifyModifiers() { | |
2449 | TypeSpec.Builder builder = | |
2450 | TypeSpec.classBuilder("Taco").addModifiers(Modifier.PUBLIC, Modifier.FINAL); | |
2451 | ||
2452 | builder.modifiers.remove(1); | |
2453 | assertThat(builder.build().modifiers).containsExactly(Modifier.PUBLIC); | |
2454 | } | |
2455 | ||
2456 | @Test | |
2457 | public void modifyFields() { | |
2458 | TypeSpec.Builder builder = TypeSpec.classBuilder("Taco") | |
2459 | .addField(int.class, "source"); | |
2460 | ||
2461 | builder.fieldSpecs.remove(0); | |
2462 | assertThat(builder.build().fieldSpecs).isEmpty(); | |
2463 | } | |
2464 | ||
2465 | @Test | |
2466 | public void modifyTypeVariables() { | |
2467 | TypeVariableName t = TypeVariableName.get("T"); | |
2468 | TypeSpec.Builder builder = | |
2469 | TypeSpec.classBuilder("Taco") | |
2470 | .addTypeVariable(t) | |
2471 | .addTypeVariable(TypeVariableName.get("V")); | |
2472 | ||
2473 | builder.typeVariables.remove(1); | |
2474 | assertThat(builder.build().typeVariables).containsExactly(t); | |
2475 | } | |
2476 | ||
2477 | @Test | |
2478 | public void modifySuperinterfaces() { | |
2479 | TypeSpec.Builder builder = TypeSpec.classBuilder("Taco") | |
2480 | .addSuperinterface(File.class); | |
2481 | ||
2482 | builder.superinterfaces.clear(); | |
2483 | assertThat(builder.build().superinterfaces).isEmpty(); | |
2484 | } | |
2485 | ||
2486 | @Test | |
2487 | public void modifyMethods() { | |
2488 | TypeSpec.Builder builder = TypeSpec.classBuilder("Taco") | |
2489 | .addMethod(MethodSpec.methodBuilder("bell").build()); | |
2490 | ||
2491 | builder.methodSpecs.clear(); | |
2492 | assertThat(builder.build().methodSpecs).isEmpty(); | |
2493 | } | |
2494 | ||
2495 | @Test | |
2496 | public void modifyTypes() { | |
2497 | TypeSpec.Builder builder = TypeSpec.classBuilder("Taco") | |
2498 | .addType(TypeSpec.classBuilder("Bell").build()); | |
2499 | ||
2500 | builder.typeSpecs.clear(); | |
2501 | assertThat(builder.build().typeSpecs).isEmpty(); | |
2502 | } | |
2503 | ||
2504 | @Test | |
2505 | public void modifyEnumConstants() { | |
2506 | TypeSpec constantType = TypeSpec.anonymousClassBuilder("").build(); | |
2507 | TypeSpec.Builder builder = TypeSpec.enumBuilder("Taco") | |
2508 | .addEnumConstant("BELL", constantType) | |
2509 | .addEnumConstant("WUT", TypeSpec.anonymousClassBuilder("").build()); | |
2510 | ||
2511 | builder.enumConstants.remove("WUT"); | |
2512 | assertThat(builder.build().enumConstants).containsExactly("BELL", constantType); | |
2513 | } | |
2514 | ||
2515 | @Test | |
2516 | public void modifyOriginatingElements() { | |
2517 | TypeSpec.Builder builder = TypeSpec.classBuilder("Taco") | |
2518 | .addOriginatingElement(Mockito.mock(Element.class)); | |
2519 | ||
2520 | builder.originatingElements.clear(); | |
2521 | assertThat(builder.build().originatingElements).isEmpty(); | |
2522 | } | |
2523 | ||
2524 | @Test public void javadocWithTrailingLineDoesNotAddAnother() { | |
2525 | TypeSpec spec = TypeSpec.classBuilder("Taco") | |
2526 | .addJavadoc("Some doc with a newline\n") | |
2527 | .build(); | |
2528 | ||
2529 | assertThat(toString(spec)).isEqualTo("" | |
2530 | + "package com.squareup.tacos;\n" | |
2531 | + "\n" | |
2532 | + "/**\n" | |
2533 | + " * Some doc with a newline\n" | |
2534 | + " */\n" | |
2535 | + "class Taco {\n" | |
2536 | + "}\n"); | |
2537 | } | |
2538 | ||
2539 | @Test public void javadocEnsuresTrailingLine() { | |
2540 | TypeSpec spec = TypeSpec.classBuilder("Taco") | |
2541 | .addJavadoc("Some doc with a newline") | |
2542 | .build(); | |
2543 | ||
2544 | assertThat(toString(spec)).isEqualTo("" | |
2545 | + "package com.squareup.tacos;\n" | |
2546 | + "\n" | |
2547 | + "/**\n" | |
2548 | + " * Some doc with a newline\n" | |
2549 | + " */\n" | |
2550 | + "class Taco {\n" | |
2551 | + "}\n"); | |
2552 | } | |
2358 | 2553 | } |