• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020 Google LLC
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 package com.google.api.generator.engine.writer;
16 
17 import com.google.api.generator.engine.ast.AnnotationNode;
18 import com.google.api.generator.engine.ast.AnonymousClassExpr;
19 import com.google.api.generator.engine.ast.ArithmeticOperationExpr;
20 import com.google.api.generator.engine.ast.ArrayExpr;
21 import com.google.api.generator.engine.ast.AssignmentExpr;
22 import com.google.api.generator.engine.ast.AssignmentOperationExpr;
23 import com.google.api.generator.engine.ast.AstNodeVisitor;
24 import com.google.api.generator.engine.ast.BlockComment;
25 import com.google.api.generator.engine.ast.BlockStatement;
26 import com.google.api.generator.engine.ast.BreakStatement;
27 import com.google.api.generator.engine.ast.CastExpr;
28 import com.google.api.generator.engine.ast.ClassDefinition;
29 import com.google.api.generator.engine.ast.CommentStatement;
30 import com.google.api.generator.engine.ast.ConcreteReference;
31 import com.google.api.generator.engine.ast.EmptyLineStatement;
32 import com.google.api.generator.engine.ast.EnumRefExpr;
33 import com.google.api.generator.engine.ast.Expr;
34 import com.google.api.generator.engine.ast.ExprStatement;
35 import com.google.api.generator.engine.ast.ForStatement;
36 import com.google.api.generator.engine.ast.GeneralForStatement;
37 import com.google.api.generator.engine.ast.IdentifierNode;
38 import com.google.api.generator.engine.ast.IfStatement;
39 import com.google.api.generator.engine.ast.InstanceofExpr;
40 import com.google.api.generator.engine.ast.JavaDocComment;
41 import com.google.api.generator.engine.ast.LambdaExpr;
42 import com.google.api.generator.engine.ast.LineComment;
43 import com.google.api.generator.engine.ast.LogicalOperationExpr;
44 import com.google.api.generator.engine.ast.MethodDefinition;
45 import com.google.api.generator.engine.ast.MethodInvocationExpr;
46 import com.google.api.generator.engine.ast.NewObjectExpr;
47 import com.google.api.generator.engine.ast.PackageInfoDefinition;
48 import com.google.api.generator.engine.ast.Reference;
49 import com.google.api.generator.engine.ast.ReferenceConstructorExpr;
50 import com.google.api.generator.engine.ast.RelationalOperationExpr;
51 import com.google.api.generator.engine.ast.ReturnExpr;
52 import com.google.api.generator.engine.ast.ScopeNode;
53 import com.google.api.generator.engine.ast.Statement;
54 import com.google.api.generator.engine.ast.SynchronizedStatement;
55 import com.google.api.generator.engine.ast.TernaryExpr;
56 import com.google.api.generator.engine.ast.ThrowExpr;
57 import com.google.api.generator.engine.ast.TryCatchStatement;
58 import com.google.api.generator.engine.ast.TypeNode;
59 import com.google.api.generator.engine.ast.UnaryOperationExpr;
60 import com.google.api.generator.engine.ast.ValueExpr;
61 import com.google.api.generator.engine.ast.VaporReference;
62 import com.google.api.generator.engine.ast.VariableExpr;
63 import com.google.api.generator.engine.ast.WhileStatement;
64 import com.google.common.base.Strings;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.TreeSet;
71 import java.util.stream.Collectors;
72 import javax.annotation.Nonnull;
73 import javax.annotation.Nullable;
74 
75 public class ImportWriterVisitor implements AstNodeVisitor {
76   private static final String DOT = ".";
77   private static final String PKG_JAVA_LANG = "java.lang";
78 
79   private final Set<String> staticImports = new TreeSet<>();
80   private final Set<String> imports = new TreeSet<>();
81 
82   // Cache the list of short names, since it will be used relatively often.
83   private final Set<String> importShortNames = new TreeSet<>();
84 
85   private String currentPackage;
86   @Nullable private String currentClassName;
87 
clear()88   public void clear() {
89     staticImports.clear();
90     imports.clear();
91     importShortNames.clear();
92   }
93 
initialize(@onnull String currentPackage)94   public void initialize(@Nonnull String currentPackage) {
95     this.currentPackage = currentPackage;
96     currentClassName = null;
97   }
98 
initialize(@onnull String currentPackage, @Nonnull String currentClassName)99   public void initialize(@Nonnull String currentPackage, @Nonnull String currentClassName) {
100     this.currentPackage = currentPackage;
101     this.currentClassName = currentClassName;
102   }
103 
write()104   public String write() {
105     // Clear out any imports duplicated across the static and non-static sets.
106     staticImports.stream().forEach(i -> imports.remove(i));
107 
108     StringBuffer sb = new StringBuffer();
109     if (!staticImports.isEmpty()) {
110       sb.append(
111           String.format("import static %s;\n\n", String.join(";\nimport static ", staticImports)));
112     }
113     if (!imports.isEmpty()) {
114       sb.append(String.format("import %s;\n\n", String.join(";\nimport ", imports)));
115     }
116     return sb.toString();
117   }
118 
collidesWithImport(String pakkage, String shortName)119   public boolean collidesWithImport(String pakkage, String shortName) {
120     // This is a sufficiently-good heuristic since it's unlikely that the AST structure has changed
121     // if the size is the same.
122     if (importShortNames.size() != imports.size()) {
123       updateShortNames();
124     }
125     return importShortNames.contains(shortName)
126         && imports.stream()
127             .filter(s -> s.equals(String.format("%s.%s", pakkage, shortName)))
128             .findFirst()
129             .orElse("")
130             .isEmpty();
131   }
132 
133   @Override
visit(IdentifierNode identifier)134   public void visit(IdentifierNode identifier) {
135     // Nothing to do.
136   }
137 
138   @Override
visit(TypeNode type)139   public void visit(TypeNode type) {
140     if (!TypeNode.isReferenceType(type)) {
141       return;
142     }
143 
144     List<Reference> refs = new ArrayList<>(type.reference().generics());
145     if (!type.reference().useFullName()) {
146       refs.add(type.reference());
147     }
148     references(refs);
149   }
150 
151   @Override
visit(ConcreteReference reference)152   public void visit(ConcreteReference reference) {
153     handleReference(reference);
154   }
155 
156   @Override
visit(VaporReference reference)157   public void visit(VaporReference reference) {
158     handleReference(reference);
159   }
160 
161   @Override
visit(ScopeNode scope)162   public void visit(ScopeNode scope) {
163     // Nothing to do.
164   }
165 
166   @Override
visit(AnnotationNode annotation)167   public void visit(AnnotationNode annotation) {
168     annotation.type().accept(this);
169     if (annotation.descriptionExprs() != null) {
170       expressions(annotation.descriptionExprs());
171     }
172   }
173 
174   @Override
visit(ArrayExpr expr)175   public void visit(ArrayExpr expr) {
176     expr.type().accept(this);
177   }
178 
179   /** =============================== EXPRESSIONS =============================== */
180   @Override
visit(ValueExpr valueExpr)181   public void visit(ValueExpr valueExpr) {
182     valueExpr.type().accept(this);
183   }
184 
185   @Override
visit(TernaryExpr ternaryExpr)186   public void visit(TernaryExpr ternaryExpr) {
187     ternaryExpr.conditionExpr().accept(this);
188     ternaryExpr.thenExpr().accept(this);
189     ternaryExpr.elseExpr().accept(this);
190   }
191 
192   @Override
visit(VariableExpr variableExpr)193   public void visit(VariableExpr variableExpr) {
194     variableExpr.variable().type().accept(this);
195     annotations(variableExpr.annotations());
196     if (variableExpr.exprReferenceExpr() != null) {
197       variableExpr.exprReferenceExpr().accept(this);
198     }
199     if (variableExpr.staticReferenceType() != null) {
200       variableExpr.staticReferenceType().accept(this);
201     }
202     variableExpr.templateNodes().stream().forEach(n -> n.accept(this));
203   }
204 
205   @Override
visit(AssignmentExpr assignmentExpr)206   public void visit(AssignmentExpr assignmentExpr) {
207     assignmentExpr.variableExpr().accept(this);
208     assignmentExpr.valueExpr().accept(this);
209   }
210 
211   @Override
visit(MethodInvocationExpr methodInvocationExpr)212   public void visit(MethodInvocationExpr methodInvocationExpr) {
213     // May not actually be used in source, but import it anyway. Unused imports will be removed by
214     // the formatter.
215     methodInvocationExpr.returnType().accept(this);
216     if (methodInvocationExpr.staticReferenceType() != null) {
217       methodInvocationExpr.staticReferenceType().accept(this);
218     }
219     if (methodInvocationExpr.exprReferenceExpr() != null) {
220       methodInvocationExpr.exprReferenceExpr().accept(this);
221     }
222     references(methodInvocationExpr.generics());
223     expressions(methodInvocationExpr.arguments());
224   }
225 
226   @Override
visit(CastExpr castExpr)227   public void visit(CastExpr castExpr) {
228     castExpr.type().accept(this);
229     castExpr.expr().accept(this);
230   }
231 
232   @Override
visit(AnonymousClassExpr anonymousClassExpr)233   public void visit(AnonymousClassExpr anonymousClassExpr) {
234     anonymousClassExpr.type().accept(this);
235     methods(anonymousClassExpr.methods());
236     statements(anonymousClassExpr.statements());
237   }
238 
239   @Override
visit(ThrowExpr throwExpr)240   public void visit(ThrowExpr throwExpr) {
241     throwExpr.type().accept(this);
242     // If throwExpr is present, then messageExpr and causeExpr will not be present. Relies on AST
243     // build-time checks.
244     if (throwExpr.throwExpr() != null) {
245       throwExpr.throwExpr().accept(this);
246       return;
247     }
248 
249     if (throwExpr.messageExpr() != null) {
250       throwExpr.messageExpr().accept(this);
251     }
252     if (throwExpr.causeExpr() != null) {
253       throwExpr.causeExpr().accept(this);
254     }
255   }
256 
257   @Override
visit(InstanceofExpr instanceofExpr)258   public void visit(InstanceofExpr instanceofExpr) {
259     instanceofExpr.expr().accept(this);
260     instanceofExpr.checkType().accept(this);
261   }
262 
263   @Override
visit(NewObjectExpr newObjectExpr)264   public void visit(NewObjectExpr newObjectExpr) {
265     newObjectExpr.type().accept(this);
266     expressions(newObjectExpr.arguments());
267   }
268 
269   @Override
visit(EnumRefExpr enumRefExpr)270   public void visit(EnumRefExpr enumRefExpr) {
271     enumRefExpr.type().accept(this);
272   }
273 
274   @Override
visit(ReturnExpr returnExpr)275   public void visit(ReturnExpr returnExpr) {
276     returnExpr.expr().accept(this);
277   }
278 
279   @Override
visit(ReferenceConstructorExpr referenceConstructorExpr)280   public void visit(ReferenceConstructorExpr referenceConstructorExpr) {
281     referenceConstructorExpr.type().accept(this);
282     expressions(referenceConstructorExpr.arguments());
283   }
284 
285   @Override
visit(ArithmeticOperationExpr arithmeticOperationExpr)286   public void visit(ArithmeticOperationExpr arithmeticOperationExpr) {
287     arithmeticOperationExpr.lhsExpr().accept(this);
288     arithmeticOperationExpr.rhsExpr().accept(this);
289   }
290 
291   @Override
visit(UnaryOperationExpr unaryOperationExpr)292   public void visit(UnaryOperationExpr unaryOperationExpr) {
293     unaryOperationExpr.expr().accept(this);
294   }
295 
296   @Override
visit(RelationalOperationExpr relationalOperationExpr)297   public void visit(RelationalOperationExpr relationalOperationExpr) {
298     relationalOperationExpr.lhsExpr().accept(this);
299     relationalOperationExpr.rhsExpr().accept(this);
300   }
301 
302   @Override
visit(LogicalOperationExpr logicalOperationExpr)303   public void visit(LogicalOperationExpr logicalOperationExpr) {
304     logicalOperationExpr.lhsExpr().accept(this);
305     logicalOperationExpr.rhsExpr().accept(this);
306   }
307 
308   @Override
visit(AssignmentOperationExpr assignmentOperationExpr)309   public void visit(AssignmentOperationExpr assignmentOperationExpr) {
310     assignmentOperationExpr.variableExpr().accept(this);
311     assignmentOperationExpr.valueExpr().accept(this);
312   }
313 
314   @Override
visit(LambdaExpr lambdaExpr)315   public void visit(LambdaExpr lambdaExpr) {
316     variableExpressions(lambdaExpr.arguments());
317     statements(lambdaExpr.body());
318     lambdaExpr.returnExpr().accept(this);
319   }
320 
321   /** =============================== STATEMENTS =============================== */
322   @Override
visit(ExprStatement exprStatement)323   public void visit(ExprStatement exprStatement) {
324     exprStatement.expression().accept(this);
325   }
326 
327   @Override
visit(BlockStatement blockStatement)328   public void visit(BlockStatement blockStatement) {
329     statements(blockStatement.body());
330   }
331 
332   @Override
visit(IfStatement ifStatement)333   public void visit(IfStatement ifStatement) {
334     ifStatement.conditionExpr().accept(this);
335     statements(ifStatement.body());
336     for (Map.Entry<Expr, List<Statement>> elseIf : ifStatement.elseIfs().entrySet()) {
337       elseIf.getKey().accept(this);
338       statements(elseIf.getValue());
339     }
340     statements(ifStatement.elseBody());
341   }
342 
343   @Override
visit(ForStatement forStatement)344   public void visit(ForStatement forStatement) {
345     forStatement.localVariableExpr().accept(this);
346     forStatement.collectionExpr().accept(this);
347     statements(forStatement.body());
348   }
349 
350   @Override
visit(GeneralForStatement generalForStatement)351   public void visit(GeneralForStatement generalForStatement) {
352     generalForStatement.initializationExpr().accept(this);
353     generalForStatement.terminationExpr().accept(this);
354     generalForStatement.updateExpr().accept(this);
355     statements(generalForStatement.body());
356   }
357 
358   @Override
visit(WhileStatement whileStatement)359   public void visit(WhileStatement whileStatement) {
360     whileStatement.conditionExpr().accept(this);
361     statements(whileStatement.body());
362   }
363 
364   @Override
visit(TryCatchStatement tryCatchStatement)365   public void visit(TryCatchStatement tryCatchStatement) {
366     if (tryCatchStatement.tryResourceExpr() != null) {
367       tryCatchStatement.tryResourceExpr().accept(this);
368     }
369     statements(tryCatchStatement.tryBody());
370     for (int i = 0; i < tryCatchStatement.catchVariableExprs().size(); i++) {
371       tryCatchStatement.catchVariableExprs().get(i).accept(this);
372       statements(tryCatchStatement.catchBlocks().get(i));
373     }
374   }
375 
376   @Override
visit(SynchronizedStatement synchronizedStatement)377   public void visit(SynchronizedStatement synchronizedStatement) {
378     synchronizedStatement.lock().accept(this);
379     statements(synchronizedStatement.body());
380   }
381 
382   @Override
visit(CommentStatement commentStatement)383   public void visit(CommentStatement commentStatement) {
384     // Nothing to do.
385   }
386 
387   @Override
visit(EmptyLineStatement emptyLineStatement)388   public void visit(EmptyLineStatement emptyLineStatement) {
389     // Nothing to do.
390   }
391 
392   @Override
visit(BreakStatement breakStatement)393   public void visit(BreakStatement breakStatement) {
394     // Nothing to do.
395   }
396 
397   /** =============================== COMMENT =============================== */
398   @Override
visit(LineComment lineComment)399   public void visit(LineComment lineComment) {
400     // Nothing to do.
401   }
402 
403   @Override
visit(BlockComment blockComment)404   public void visit(BlockComment blockComment) {
405     // Nothing to do.
406   }
407 
408   @Override
visit(JavaDocComment javaDocComment)409   public void visit(JavaDocComment javaDocComment) {
410     // Nothing to do.
411   }
412 
413   /** =============================== OTHER =============================== */
414   @Override
visit(MethodDefinition methodDefinition)415   public void visit(MethodDefinition methodDefinition) {
416     methodDefinition.returnType().accept(this);
417     annotations(methodDefinition.annotations());
418     types(methodDefinition.throwsExceptions());
419     variableExpressions(methodDefinition.arguments());
420     statements(methodDefinition.body());
421     if (methodDefinition.returnExpr() != null) {
422       methodDefinition.returnExpr().accept(this);
423     }
424   }
425 
426   @Override
visit(ClassDefinition classDefinition)427   public void visit(ClassDefinition classDefinition) {
428     annotations(classDefinition.annotations());
429     types(classDefinition.implementsTypes());
430     if (classDefinition.extendsType() != null) {
431       classDefinition.extendsType().accept(this);
432     }
433     statements(classDefinition.statements());
434     for (MethodDefinition method : classDefinition.methods()) {
435       method.accept(this);
436     }
437     for (ClassDefinition nestedClass : classDefinition.nestedClasses()) {
438       nestedClass.accept(this);
439     }
440   }
441 
442   @Override
visit(PackageInfoDefinition packageInfoDefinition)443   public void visit(PackageInfoDefinition packageInfoDefinition) {
444     annotations(packageInfoDefinition.annotations());
445   }
446 
447   /** =============================== PRIVATE HELPERS =============================== */
addImport(String packageToImport)448   private void addImport(String packageToImport) {
449     String shortName = packageToImport.substring(packageToImport.lastIndexOf(DOT) + 1);
450     if (importShortNames.contains(shortName)) {
451       return;
452     }
453     importShortNames.add(shortName);
454     imports.add(packageToImport);
455   }
456 
updateShortNames()457   private void updateShortNames() {
458     importShortNames.clear();
459     importShortNames.addAll(
460         imports.stream().map(s -> s.substring(s.lastIndexOf(DOT) + 1)).collect(Collectors.toSet()));
461   }
462 
annotations(List<AnnotationNode> annotations)463   private void annotations(List<AnnotationNode> annotations) {
464     for (AnnotationNode annotation : annotations) {
465       annotation.accept(this);
466     }
467   }
468 
expressions(List<Expr> expressions)469   private void expressions(List<Expr> expressions) {
470     for (Expr expr : expressions) {
471       expr.accept(this);
472     }
473   }
474 
variableExpressions(List<VariableExpr> expressions)475   private void variableExpressions(List<VariableExpr> expressions) {
476     for (VariableExpr expr : expressions) {
477       expr.accept(this);
478     }
479   }
480 
handleReference(Reference reference)481   private void handleReference(Reference reference) {
482     // Don't need to import this.
483     if (reference.useFullName()) {
484       return;
485     }
486     if (!reference.isStaticImport()
487         && (reference.isFromPackage(PKG_JAVA_LANG) || reference.isFromPackage(currentPackage))) {
488       return;
489     }
490 
491     if (reference.isWildcard()) {
492       if (reference.wildcardUpperBound() != null) {
493         references(Arrays.asList(reference.wildcardUpperBound()));
494       }
495       return;
496     }
497 
498     if (reference.isStaticImport()
499         && !Strings.isNullOrEmpty(currentClassName)
500         && !reference.enclosingClassNames().isEmpty()
501         && reference.enclosingClassNames().contains(currentClassName)) {
502       return;
503     }
504 
505     if (reference.isStaticImport()) {
506       // TODO(miraleung): This should have a variant of addImports as well. Handle static import
507       // collisions.
508       staticImports.add(reference.fullName());
509     } else {
510       if (reference.hasEnclosingClass()) {
511         addImport(
512             String.format(
513                 "%s.%s", reference.pakkage(), String.join(DOT, reference.enclosingClassNames())));
514       } else {
515         addImport(reference.fullName());
516       }
517     }
518 
519     references(reference.generics());
520   }
521 
references(List<Reference> refs)522   private void references(List<Reference> refs) {
523     for (Reference ref : refs) {
524       ref.accept(this);
525     }
526   }
527 
statements(List<Statement> statements)528   private void statements(List<Statement> statements) {
529     for (Statement statement : statements) {
530       statement.accept(this);
531     }
532   }
533 
methods(List<MethodDefinition> methods)534   private void methods(List<MethodDefinition> methods) {
535     for (MethodDefinition method : methods) {
536       method.accept(this);
537     }
538   }
539 
types(List<TypeNode> types)540   private void types(List<TypeNode> types) {
541     for (TypeNode type : types) {
542       type.accept(this);
543     }
544   }
545 }
546