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