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