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.api.generator.engine.lexicon.Keyword;
18 import com.google.auto.value.AutoValue;
19 import com.google.common.base.Preconditions;
20 import com.google.common.collect.ImmutableList;
21 import java.util.Collections;
22 import java.util.LinkedHashSet;
23 import java.util.List;
24 import java.util.stream.Collectors;
25 import javax.annotation.Nullable;
26 
27 @AutoValue
28 public abstract class VariableExpr implements Expr {
variable()29   public abstract Variable variable();
30 
31   @Nullable
exprReferenceExpr()32   public abstract Expr exprReferenceExpr();
33 
34   @Nullable
staticReferenceType()35   public abstract TypeNode staticReferenceType();
36 
37   /** Variable declaration fields. */
isDecl()38   public abstract boolean isDecl();
39 
scope()40   public abstract ScopeNode scope();
41 
isStatic()42   public abstract boolean isStatic();
43 
isFinal()44   public abstract boolean isFinal();
45 
isVolatile()46   public abstract boolean isVolatile();
47 
48   // Optional
annotations()49   public abstract ImmutableList<AnnotationNode> annotations();
50 
51   // Please use this only in conjunction with methods.
52   // Supports only parameterized types like Map<K, V>.
53   // TODO(unsupported): Fully generic arguments, e.g. foobar(K key, V value).
54   // This list can contain only IdentifierNode or TypeNode.
templateNodes()55   public abstract ImmutableList<AstNode> templateNodes();
56 
57   // Private.
58   // Can either contain String or TypeNode objects.
templateObjects()59   abstract ImmutableList<Object> templateObjects();
60 
61   @Override
type()62   public TypeNode type() {
63     if (isDecl()) {
64       return TypeNode.VOID;
65     }
66     return variable().type();
67   }
68 
69   @Override
accept(AstNodeVisitor visitor)70   public void accept(AstNodeVisitor visitor) {
71     visitor.visit(this);
72   }
73 
withVariable(Variable variable)74   public static VariableExpr withVariable(Variable variable) {
75     return builder().setVariable(variable).build();
76   }
77 
builder()78   public static Builder builder() {
79     return new AutoValue_VariableExpr.Builder()
80         .setIsDecl(false)
81         .setIsFinal(false)
82         .setIsStatic(false)
83         .setIsVolatile(false)
84         .setScope(ScopeNode.LOCAL)
85         .setTemplateObjects(ImmutableList.of())
86         .setAnnotations(Collections.emptyList());
87   }
88 
toBuilder()89   public abstract Builder toBuilder();
90 
91   @AutoValue.Builder
92   public abstract static class Builder {
setVariable(Variable variable)93     public abstract Builder setVariable(Variable variable);
94 
95     // Optional, but cannot co-exist with a variable declaration.
setExprReferenceExpr(Expr exprReference)96     public abstract Builder setExprReferenceExpr(Expr exprReference);
97 
98     // Optional, but cannot co-exist with an expression reference.
setStaticReferenceType(TypeNode type)99     public abstract Builder setStaticReferenceType(TypeNode type);
100 
setIsDecl(boolean isDecl)101     public abstract Builder setIsDecl(boolean isDecl);
102 
setScope(ScopeNode scope)103     public abstract Builder setScope(ScopeNode scope);
104 
setIsStatic(boolean isStatic)105     public abstract Builder setIsStatic(boolean isStatic);
106 
setIsFinal(boolean isFinal)107     public abstract Builder setIsFinal(boolean isFinal);
108 
setIsVolatile(boolean isVolatile)109     public abstract Builder setIsVolatile(boolean isVolatile);
110 
setAnnotations(List<AnnotationNode> annotations)111     public abstract Builder setAnnotations(List<AnnotationNode> annotations);
112 
annotations()113     abstract ImmutableList<AnnotationNode> annotations();
114 
115     // This should be used only for method arguments.
setTemplateObjects(List<Object> objects)116     public abstract Builder setTemplateObjects(List<Object> objects);
117 
118     // Private.
setTemplateNodes(List<AstNode> nodes)119     abstract Builder setTemplateNodes(List<AstNode> nodes);
120 
variable()121     abstract Variable variable();
122 
templateObjects()123     abstract ImmutableList<Object> templateObjects();
124 
autoBuild()125     abstract VariableExpr autoBuild();
126 
build()127     public VariableExpr build() {
128       NodeValidator.checkNoNullElements(
129           templateObjects(),
130           "template objects",
131           String.format("variable expr %s", variable().identifier().name()));
132       setTemplateNodes(
133           templateObjects().stream()
134               .map(
135                   o -> {
136                     Preconditions.checkState(
137                         o instanceof String || o instanceof TypeNode,
138                         "Template objects can only be Strings or Typenodes");
139                     if (o instanceof String) {
140                       return (AstNode) IdentifierNode.withName((String) o);
141                     }
142                     return (AstNode) o;
143                   })
144               .collect(Collectors.toList()));
145 
146       // Remove duplicates while maintaining insertion order.
147       ImmutableList<AnnotationNode> processedAnnotations = annotations();
148       setAnnotations(
149           new LinkedHashSet<>(processedAnnotations).stream().collect(Collectors.toList()));
150 
151       VariableExpr variableExpr = autoBuild();
152 
153       // TODO: should match on AnnotationNode @Target of ElementType.FIELD
154       if (!variableExpr.isDecl()) {
155         Preconditions.checkState(
156             variableExpr.annotations().isEmpty(),
157             "Annotation can only be added to variable declaration.");
158       }
159 
160       if (variableExpr.isDecl() || variableExpr.exprReferenceExpr() != null) {
161         Preconditions.checkState(
162             variableExpr.isDecl() ^ (variableExpr.exprReferenceExpr() != null),
163             "Variable references cannot be declared");
164       }
165 
166       Preconditions.checkState(
167           variableExpr.exprReferenceExpr() == null || variableExpr.staticReferenceType() == null,
168           "Only the expression reference or the static reference can be set, not both");
169 
170       if (variableExpr.exprReferenceExpr() != null) {
171         Preconditions.checkState(
172             TypeNode.isReferenceType(variableExpr.exprReferenceExpr().type()),
173             "Variables can only be referenced from object types");
174       }
175       if (variableExpr.staticReferenceType() != null) {
176         Preconditions.checkState(
177             TypeNode.isReferenceType(variableExpr.staticReferenceType()),
178             String.format(
179                 "Static field references can only be done on static types, but instead found %s",
180                 variableExpr.staticReferenceType()));
181       }
182 
183       // A variable name of "class" is valid only when it's a static reference.
184       String varName = variableExpr.variable().identifier().name();
185       if (Keyword.isKeyword(varName)) {
186         Preconditions.checkState(
187             variableExpr.staticReferenceType() != null
188                 || (variableExpr.exprReferenceExpr() != null
189                     && TypeNode.isReferenceType(variableExpr.exprReferenceExpr().type())),
190             String.format(
191                 "Variable field name %s is invalid on non-static or non-reference types", varName));
192       }
193 
194       return variableExpr;
195     }
196   }
197 }
198