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.ast; 16 17 import com.google.auto.value.AutoValue; 18 import com.google.common.base.Preconditions; 19 import com.google.common.collect.ImmutableList; 20 import java.util.Arrays; 21 import java.util.Collections; 22 import java.util.LinkedHashSet; 23 import java.util.List; 24 import java.util.Set; 25 import java.util.stream.Collectors; 26 import javax.annotation.Nullable; 27 28 @AutoValue 29 public abstract class MethodDefinition implements AstNode { 30 static final Reference RUNTIME_EXCEPTION_REFERENCE = 31 ConcreteReference.withClazz(RuntimeException.class); 32 33 // Required. scope()34 public abstract ScopeNode scope(); 35 // Required. returnType()36 public abstract TypeNode returnType(); 37 // Required. methodIdentifier()38 public abstract IdentifierNode methodIdentifier(); 39 headerCommentStatements()40 public abstract ImmutableList<CommentStatement> headerCommentStatements(); 41 annotations()42 public abstract ImmutableList<AnnotationNode> annotations(); 43 44 // Using a list helps with determinism in unit tests. throwsExceptions()45 public abstract ImmutableList<TypeNode> throwsExceptions(); 46 arguments()47 public abstract ImmutableList<VariableExpr> arguments(); 48 isStatic()49 public abstract boolean isStatic(); 50 isFinal()51 public abstract boolean isFinal(); 52 isAbstract()53 public abstract boolean isAbstract(); 54 isConstructor()55 public abstract boolean isConstructor(); 56 body()57 public abstract ImmutableList<Statement> body(); 58 59 // Please use VariableExpr for templating individual arguments. templateIdentifiers()60 public abstract ImmutableList<IdentifierNode> templateIdentifiers(); 61 returnTemplateIdentifiers()62 public abstract ImmutableList<IdentifierNode> returnTemplateIdentifiers(); 63 64 // Private accessors. templateNames()65 abstract ImmutableList<String> templateNames(); 66 returnTemplateNames()67 abstract ImmutableList<String> returnTemplateNames(); 68 69 @Nullable returnExpr()70 public abstract ReturnExpr returnExpr(); 71 isOverride()72 abstract boolean isOverride(); 73 74 @Nullable name()75 abstract String name(); 76 77 @Override accept(AstNodeVisitor visitor)78 public void accept(AstNodeVisitor visitor) { 79 visitor.visit(this); 80 } 81 toBuilder()82 public abstract Builder toBuilder(); 83 builder()84 public static Builder builder() { 85 return new AutoValue_MethodDefinition.Builder() 86 .setArguments(Collections.emptyList()) 87 .setIsAbstract(false) 88 .setIsFinal(false) 89 .setIsStatic(false) 90 .setIsConstructor(false) 91 .setHeaderCommentStatements(Collections.emptyList()) 92 .setAnnotations(Collections.emptyList()) 93 .setThrowsExceptions(Collections.emptyList()) 94 .setBody(Collections.emptyList()) 95 .setIsOverride(false) 96 .setTemplateNames(ImmutableList.of()) 97 .setReturnTemplateNames(ImmutableList.of()); 98 } 99 constructorBuilder()100 public static Builder constructorBuilder() { 101 return new AutoValue_MethodDefinition.Builder() 102 .setArguments(Collections.emptyList()) 103 .setIsAbstract(false) 104 .setIsFinal(false) 105 .setIsStatic(false) 106 .setIsConstructor(true) 107 .setHeaderCommentStatements(Collections.emptyList()) 108 .setAnnotations(Collections.emptyList()) 109 .setThrowsExceptions(Collections.emptyList()) 110 .setBody(Collections.emptyList()) 111 .setIsOverride(false) 112 .setTemplateNames(ImmutableList.of()) 113 .setReturnTemplateNames(ImmutableList.of()); 114 } 115 116 @AutoValue.Builder 117 public abstract static class Builder { setScope(ScopeNode scope)118 public abstract Builder setScope(ScopeNode scope); 119 setReturnType(TypeNode type)120 public abstract Builder setReturnType(TypeNode type); 121 setName(String name)122 public abstract Builder setName(String name); 123 setHeaderCommentStatements(CommentStatement... comments)124 public Builder setHeaderCommentStatements(CommentStatement... comments) { 125 return setHeaderCommentStatements(Arrays.asList(comments)); 126 } 127 setHeaderCommentStatements( List<CommentStatement> headeCommentStatements)128 public abstract Builder setHeaderCommentStatements( 129 List<CommentStatement> headeCommentStatements); 130 setAnnotations(List<AnnotationNode> annotations)131 public abstract Builder setAnnotations(List<AnnotationNode> annotations); 132 setIsStatic(boolean isStatic)133 public abstract Builder setIsStatic(boolean isStatic); 134 setIsFinal(boolean isFinal)135 public abstract Builder setIsFinal(boolean isFinal); 136 setIsAbstract(boolean isAbstract)137 public abstract Builder setIsAbstract(boolean isAbstract); 138 setIsConstructor(boolean isConstructor)139 public abstract Builder setIsConstructor(boolean isConstructor); 140 setThrowsExceptions(List<TypeNode> exceptionTypes)141 public abstract Builder setThrowsExceptions(List<TypeNode> exceptionTypes); 142 setArguments(VariableExpr... arguments)143 public Builder setArguments(VariableExpr... arguments) { 144 return setArguments(Arrays.asList(arguments)); 145 } 146 setArguments(List<VariableExpr> arguments)147 public abstract Builder setArguments(List<VariableExpr> arguments); 148 setBody(List<Statement> body)149 public abstract Builder setBody(List<Statement> body); 150 setReturnExpr(Expr expr)151 public Builder setReturnExpr(Expr expr) { 152 return setReturnExpr(ReturnExpr.withExpr(expr)); 153 } 154 setReturnExpr(ReturnExpr returnExpr)155 public abstract Builder setReturnExpr(ReturnExpr returnExpr); 156 setIsOverride(boolean isOverride)157 public abstract Builder setIsOverride(boolean isOverride); 158 setTemplateNames(List<String> names)159 public abstract Builder setTemplateNames(List<String> names); 160 setReturnTemplateNames(List<String> names)161 public abstract Builder setReturnTemplateNames(List<String> names); 162 163 // Private. setTemplateIdentifiers(List<IdentifierNode> identifiers)164 abstract Builder setTemplateIdentifiers(List<IdentifierNode> identifiers); 165 setReturnTemplateIdentifiers(List<IdentifierNode> identifiers)166 abstract Builder setReturnTemplateIdentifiers(List<IdentifierNode> identifiers); 167 setMethodIdentifier(IdentifierNode methodIdentifier)168 abstract Builder setMethodIdentifier(IdentifierNode methodIdentifier); 169 170 // Private accessors. name()171 abstract String name(); 172 arguments()173 abstract ImmutableList<VariableExpr> arguments(); 174 headerCommentStatements()175 abstract ImmutableList<CommentStatement> headerCommentStatements(); 176 annotations()177 abstract ImmutableList<AnnotationNode> annotations(); 178 throwsExceptions()179 abstract ImmutableList<TypeNode> throwsExceptions(); 180 returnType()181 abstract TypeNode returnType(); 182 isOverride()183 abstract boolean isOverride(); 184 isAbstract()185 abstract boolean isAbstract(); 186 isFinal()187 abstract boolean isFinal(); 188 isStatic()189 abstract boolean isStatic(); 190 body()191 abstract ImmutableList<Statement> body(); 192 isConstructor()193 abstract boolean isConstructor(); 194 scope()195 abstract ScopeNode scope(); 196 autoBuild()197 abstract MethodDefinition autoBuild(); 198 templateNames()199 abstract ImmutableList<String> templateNames(); 200 returnTemplateNames()201 abstract ImmutableList<String> returnTemplateNames(); 202 build()203 public MethodDefinition build() { 204 performNullChecks(); 205 206 // Handle templates. 207 setTemplateIdentifiers( 208 templateNames().stream() 209 .map(n -> IdentifierNode.withName(n)) 210 .collect(Collectors.toList())); 211 212 if (!returnTemplateNames().isEmpty()) { 213 Preconditions.checkState( 214 TypeNode.isReferenceType(returnType()), "Primitive return types cannot be templated"); 215 } 216 setReturnTemplateIdentifiers( 217 returnTemplateNames().stream() 218 .map( 219 n -> { 220 Preconditions.checkState( 221 templateNames().contains(n), 222 String.format( 223 "Return template name %s not found in method template names", n)); 224 return IdentifierNode.withName(n); 225 }) 226 .collect(Collectors.toList())); 227 228 // Constructor checks. 229 if (isConstructor()) { 230 Preconditions.checkState( 231 TypeNode.isReferenceType(returnType()), "Constructor must return an object type."); 232 setName(returnType().reference().name()); 233 } else { 234 Preconditions.checkNotNull(name(), "Methods must have a name"); 235 } 236 237 IdentifierNode methodIdentifier = IdentifierNode.builder().setName(name()).build(); 238 setMethodIdentifier(methodIdentifier); 239 240 // Abstract and modifier checking. 241 if (isAbstract()) { 242 Preconditions.checkState( 243 !isFinal() && !isStatic() && !scope().equals(ScopeNode.PRIVATE), 244 "Abstract methods cannot be static, final, or private"); 245 } 246 247 // If this method overrides another, ensure that the Override annotation is the last one. 248 ImmutableList<AnnotationNode> processedAnnotations = annotations(); 249 if (isOverride()) { 250 processedAnnotations = 251 ImmutableList.<AnnotationNode>builder() 252 .addAll(annotations()) 253 .add(AnnotationNode.OVERRIDE) 254 .build(); 255 } 256 // Remove duplicates while maintaining insertion order. 257 setAnnotations( 258 new LinkedHashSet<>(processedAnnotations).stream().collect(Collectors.toList())); 259 260 MethodDefinition method = autoBuild(); 261 262 Preconditions.checkState( 263 !method.scope().equals(ScopeNode.LOCAL), 264 "Method scope must be either public, protected, or private"); 265 266 Preconditions.checkState( 267 !method.returnType().equals(TypeNode.NULL), "Null is not a valid method return type"); 268 269 // Constructor checking. 270 if (method.isConstructor()) { 271 Preconditions.checkState( 272 !method.isFinal() && !method.isStatic(), "Constructors cannot be static or final"); 273 Preconditions.checkState(!method.isOverride(), "A constructor cannot override another"); 274 Preconditions.checkState( 275 method.returnExpr() == null, "A constructor cannot have a return expression"); 276 // Reference already checked at method name validation time. 277 // TODO(unsupported): Constructors for templated types. This would require changing the 278 // Reference API, which would be trivial. However, such constructors don't seem to be needed 279 // yet. 280 Preconditions.checkState( 281 method.returnType().reference().generics().isEmpty(), 282 "Constructors for templated classes are not yet supported"); 283 } else { 284 // Return type validation and checking. 285 boolean isLastStatementThrowExpr = false; 286 Statement lastStatement; 287 if (!body().isEmpty() 288 && (lastStatement = body().get(body().size() - 1)) instanceof ExprStatement) { 289 isLastStatementThrowExpr = 290 ((ExprStatement) lastStatement).expression() instanceof ThrowExpr; 291 } 292 if (!method.returnType().equals(TypeNode.VOID) && !isLastStatementThrowExpr) { 293 Preconditions.checkNotNull( 294 method.returnExpr(), 295 "Method with non-void return type must have a return expression"); 296 } 297 298 if (!method.returnType().equals(TypeNode.VOID) && !isLastStatementThrowExpr) { 299 Preconditions.checkNotNull( 300 method.returnExpr(), 301 "Method with non-void return type must have a return expression"); 302 } 303 304 if (method.returnExpr() != null && !isLastStatementThrowExpr) { 305 if (method.returnType().isPrimitiveType() 306 || method.returnExpr().expr().type().isPrimitiveType()) { 307 Preconditions.checkState( 308 method.returnType().equals((method.returnExpr().expr().type())), 309 "Method return type does not match the return expression type"); 310 } else { 311 Preconditions.checkState( 312 method.returnType().isSupertypeOrEquals(method.returnExpr().expr().type()), 313 "Method reference return type is not a supertype of the return expression type"); 314 } 315 } 316 } 317 318 performArgumentChecks(); 319 performThrownExceptionChecks(); 320 321 return method; 322 } 323 performArgumentChecks()324 private void performArgumentChecks() { 325 // Must be a declaration. 326 arguments().stream() 327 .forEach( 328 varExpr -> 329 Preconditions.checkState( 330 varExpr.isDecl(), 331 String.format( 332 "Argument %s must be a variable declaration", 333 varExpr.variable().identifier()))); 334 // No modifiers allowed. 335 arguments().stream() 336 .forEach( 337 varExpr -> 338 Preconditions.checkState( 339 varExpr.scope().equals(ScopeNode.LOCAL) 340 && !varExpr.isStatic() 341 && !varExpr.isVolatile(), 342 String.format( 343 "Argument %s must have local scope, and cannot have \"static\" or" 344 + " \"volatile\" modifiers", 345 varExpr.variable().identifier()))); 346 347 // Check that there aren't any arguments with duplicate names. 348 List<String> allArgNames = 349 arguments().stream() 350 .map(v -> v.variable().identifier().name()) 351 .collect(Collectors.toList()); 352 Set<String> duplicateArgNames = 353 allArgNames.stream() 354 .filter(n -> Collections.frequency(allArgNames, n) > 1) 355 .collect(Collectors.toSet()); 356 Preconditions.checkState( 357 duplicateArgNames.isEmpty(), 358 String.format( 359 "Lambda arguments cannot have duplicate names: %s", duplicateArgNames.toString())); 360 } 361 performThrownExceptionChecks()362 private void performThrownExceptionChecks() { 363 throwsExceptions().stream() 364 .forEach( 365 exceptionType -> { 366 Preconditions.checkState( 367 TypeNode.isExceptionType(exceptionType), 368 String.format("Type %s is not an exception type", exceptionType.reference())); 369 Preconditions.checkState( 370 !RUNTIME_EXCEPTION_REFERENCE.isAssignableFrom(exceptionType.reference()), 371 String.format( 372 "RuntimeException type %s does not need to be thrown", 373 exceptionType.reference().name())); 374 }); 375 } 376 performNullChecks()377 private void performNullChecks() { 378 String contextInfo = String.format("method definition of %s", name()); 379 NodeValidator.checkNoNullElements(headerCommentStatements(), "header comments", contextInfo); 380 NodeValidator.checkNoNullElements(annotations(), "annotations", contextInfo); 381 NodeValidator.checkNoNullElements(throwsExceptions(), "declared exceptions", contextInfo); 382 NodeValidator.checkNoNullElements(body(), "body", contextInfo); 383 NodeValidator.checkNoNullElements(templateNames(), "template names", contextInfo); 384 NodeValidator.checkNoNullElements( 385 returnTemplateNames(), "return template names", contextInfo); 386 } 387 } 388 } 389