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.annotations.VisibleForTesting;
19 import com.google.common.base.Preconditions;
20 import com.google.protobuf.ByteString;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Objects;
24 import javax.annotation.Nullable;
25 
26 @AutoValue
27 public abstract class TypeNode implements AstNode, Comparable<TypeNode> {
28   static final Reference EXCEPTION_REFERENCE = ConcreteReference.withClazz(Exception.class);
29   public static final Reference WILDCARD_REFERENCE = ConcreteReference.wildcard();
30 
31   public enum TypeKind {
32     BYTE,
33     SHORT,
34     INT,
35     LONG,
36     FLOAT,
37     DOUBLE,
38     BOOLEAN,
39     CHAR,
40     OBJECT,
41     VOID
42   }
43 
44   public static final TypeNode BOOLEAN = builder().setTypeKind(TypeKind.BOOLEAN).build();
45   public static final TypeNode BYTE = builder().setTypeKind(TypeKind.BYTE).build();
46   public static final TypeNode CHAR = builder().setTypeKind(TypeKind.CHAR).build();
47   public static final TypeNode DOUBLE = builder().setTypeKind(TypeKind.DOUBLE).build();
48   public static final TypeNode FLOAT = builder().setTypeKind(TypeKind.FLOAT).build();
49   public static final TypeNode INT = builder().setTypeKind(TypeKind.INT).build();
50   public static final TypeNode LONG = builder().setTypeKind(TypeKind.LONG).build();
51   public static final TypeNode SHORT = builder().setTypeKind(TypeKind.SHORT).build();
52 
53   public static final TypeNode BOOLEAN_OBJECT =
54       withReference(ConcreteReference.withClazz(Boolean.class));
55   public static final TypeNode BYTE_OBJECT = withReference(ConcreteReference.withClazz(Byte.class));
56   public static final TypeNode CHAR_OBJECT =
57       withReference(ConcreteReference.withClazz(Character.class));
58   public static final TypeNode DOUBLE_OBJECT =
59       withReference(ConcreteReference.withClazz(Double.class));
60   public static final TypeNode FLOAT_OBJECT =
61       withReference(ConcreteReference.withClazz(Float.class));
62   public static final TypeNode INT_OBJECT =
63       withReference(ConcreteReference.withClazz(Integer.class));
64   public static final TypeNode LONG_OBJECT = withReference(ConcreteReference.withClazz(Long.class));
65   public static final TypeNode SHORT_OBJECT =
66       withReference(ConcreteReference.withClazz(Short.class));
67 
68   public static final TypeNode CLASS_OBJECT =
69       withReference(ConcreteReference.withClazz(Class.class));
70 
71   public static final TypeNode BYTESTRING =
72       TypeNode.withReference(ConcreteReference.withClazz(ByteString.class));
73   public static final TypeNode VALUE =
74       withReference(
75           VaporReference.builder().setName("Value").setPakkage("com.google.protobuf").build());
76 
77   private static final Map<TypeNode, TypeNode> BOXED_TYPE_MAP = createBoxedTypeMap();
78 
79   public static final TypeNode VOID = builder().setTypeKind(TypeKind.VOID).build();
80 
81   public static final TypeNode NULL =
82       withReference(ConcreteReference.withClazz(javax.lang.model.type.NullType.class));
83   public static final TypeNode OBJECT = withReference(ConcreteReference.withClazz(Object.class));
84   public static final TypeNode STRING = withReference(ConcreteReference.withClazz(String.class));
85   public static final TypeNode VOID_OBJECT = withReference(ConcreteReference.withClazz(Void.class));
86 
87   public static final TypeNode THROWABLE =
88       withReference(ConcreteReference.withClazz(Throwable.class));
89 
90   public static final TypeNode DEPRECATED =
91       withReference(ConcreteReference.withClazz(Deprecated.class));
92 
93   public static final TypeNode STRING_ARRAY =
94       builder()
95           .setTypeKind(TypeKind.OBJECT)
96           .setReference(ConcreteReference.withClazz(String.class))
97           .setIsArray(true)
98           .build();
99 
typeKind()100   public abstract TypeKind typeKind();
101 
isArray()102   public abstract boolean isArray();
103 
createArrayTypeOf(TypeNode type)104   public static TypeNode createArrayTypeOf(TypeNode type) {
105     return builder()
106         .setTypeKind(type.typeKind())
107         .setReference(type.reference())
108         .setIsArray(true)
109         .build();
110   }
111 
createElementTypeFromArrayType(TypeNode type)112   public static TypeNode createElementTypeFromArrayType(TypeNode type) {
113     Preconditions.checkArgument(type.isArray(), "Input type must be an array");
114     return builder()
115         .setTypeKind(type.typeKind())
116         .setReference(type.reference())
117         .setIsArray(false)
118         .build();
119   }
120 
121   @Nullable
reference()122   public abstract Reference reference();
123 
124   @Override
compareTo(TypeNode other)125   public int compareTo(TypeNode other) {
126     // Ascending order of name.
127     if (isPrimitiveType()) {
128       if (other.isPrimitiveType()) {
129         return typeKind().name().compareTo(other.typeKind().name());
130       }
131       // b is a reference type or null, so a < b.
132       return -1;
133     }
134 
135     if (equals(TypeNode.NULL)) {
136       // Can't self-compare, so we don't need to check whether the other one is TypeNode.NULL.
137       return other.isPrimitiveType() ? 1 : -1;
138     }
139 
140     if (other.isPrimitiveType() || other.equals(TypeNode.NULL)) {
141       return 1;
142     }
143 
144     // Both are reference types.
145     // TODO(miraleung): Replace this with a proper reference Comaparator.
146     return reference().fullName().compareTo(other.reference().fullName());
147   }
148 
builder()149   public static Builder builder() {
150     return new AutoValue_TypeNode.Builder().setIsArray(false);
151   }
152 
153   @AutoValue.Builder
154   public abstract static class Builder {
setTypeKind(TypeKind typeKind)155     public abstract Builder setTypeKind(TypeKind typeKind);
156 
setIsArray(boolean isArray)157     public abstract Builder setIsArray(boolean isArray);
158 
setReference(Reference reference)159     public abstract Builder setReference(Reference reference);
160 
161     // Private.
reference()162     abstract Reference reference();
163 
autoBuild()164     abstract TypeNode autoBuild();
165 
build()166     public TypeNode build() {
167       if (reference() != null) {
168         // Disallow top-level wildcard references.
169         Preconditions.checkState(
170             !reference().isWildcard(),
171             String.format(
172                 "The top-level referenece in a type cannot be a wildcard, found %s",
173                 reference().name()));
174       }
175       return autoBuild();
176     }
177   }
178 
179   // TODO(miraleung): More type creation helpers to come...
withReference(Reference reference)180   public static TypeNode withReference(Reference reference) {
181     return TypeNode.builder().setTypeKind(TypeKind.OBJECT).setReference(reference).build();
182   }
183 
withExceptionClazz(Class<?> clazz)184   public static TypeNode withExceptionClazz(Class<?> clazz) {
185     Preconditions.checkState(Exception.class.isAssignableFrom(clazz));
186     return withReference(ConcreteReference.withClazz(clazz));
187   }
188 
isExceptionType(TypeNode type)189   public static boolean isExceptionType(TypeNode type) {
190     return isReferenceType(type) && EXCEPTION_REFERENCE.isAssignableFrom(type.reference());
191   }
192 
isReferenceType(TypeNode type)193   public static boolean isReferenceType(TypeNode type) {
194     return !isPrimitiveType(type.typeKind())
195         && type.typeKind().equals(TypeKind.OBJECT)
196         && type.reference() != null
197         && !type.equals(TypeNode.NULL);
198   }
199 
isNumericType(TypeNode type)200   public static boolean isNumericType(TypeNode type) {
201     return type.equals(TypeNode.INT)
202         || type.equals(TypeNode.LONG)
203         || type.equals(TypeNode.DOUBLE)
204         || type.equals(TypeNode.SHORT)
205         || type.equals(TypeNode.FLOAT)
206         || type.equals(TypeNode.CHAR)
207         || type.equals(TypeNode.BYTE);
208   }
209 
isFloatingPointType(TypeNode type)210   public static boolean isFloatingPointType(TypeNode type) {
211     return type.equals(TypeNode.DOUBLE) || type.equals(TypeNode.FLOAT);
212   }
213 
isBoxedType(TypeNode type)214   public static boolean isBoxedType(TypeNode type) {
215     return isReferenceType(type) && BOXED_TYPE_MAP.containsValue(type);
216   }
217 
isPrimitiveType()218   public boolean isPrimitiveType() {
219     return isPrimitiveType(typeKind());
220   }
221 
isProtoPrimitiveType()222   public boolean isProtoPrimitiveType() {
223     return isPrimitiveType() || equals(TypeNode.STRING) || equals(TypeNode.BYTESTRING);
224   }
225 
isProtoEmptyType()226   public boolean isProtoEmptyType() {
227     return reference().pakkage().equals("com.google.protobuf")
228         && reference().name().equals("Empty");
229   }
230 
isSupertypeOrEquals(TypeNode other)231   public boolean isSupertypeOrEquals(TypeNode other) {
232     boolean oneTypeIsNull = equals(TypeNode.NULL) ^ other.equals(TypeNode.NULL);
233     return !isPrimitiveType()
234         && !other.isPrimitiveType()
235         && isArray() == other.isArray()
236         && (reference().isSupertypeOrEquals(other.reference()) || oneTypeIsNull);
237   }
238 
239   @Override
accept(AstNodeVisitor visitor)240   public void accept(AstNodeVisitor visitor) {
241     visitor.visit(this);
242   }
243 
244   // Java overrides.
245   @Override
equals(Object o)246   public boolean equals(Object o) {
247     if (!(o instanceof TypeNode)) {
248       return false;
249     }
250 
251     TypeNode type = (TypeNode) o;
252     return strictEquals(type) || isBoxedTypeEquals(type);
253   }
254 
255   @Override
hashCode()256   public int hashCode() {
257     int hash = 17 * typeKind().hashCode() + 19 * (isArray() ? 1 : 3);
258     if (reference() != null) {
259       hash += 23 * reference().hashCode();
260     }
261     return hash;
262   }
263 
264   @VisibleForTesting
strictEquals(TypeNode other)265   boolean strictEquals(TypeNode other) {
266     return typeKind().equals(other.typeKind())
267         && (isArray() == other.isArray())
268         && Objects.equals(reference(), other.reference());
269   }
270 
271   @VisibleForTesting
isBoxedTypeEquals(TypeNode other)272   boolean isBoxedTypeEquals(TypeNode other) {
273     // If both types are primitive/reference type, return false.
274     // Array of boxed/primitive type is not considered equal.
275     if (isArray() || other.isArray()) {
276       return false;
277     }
278     if (other.isPrimitiveType()) {
279       return Objects.equals(this, BOXED_TYPE_MAP.get(other));
280     }
281     return Objects.equals(other, BOXED_TYPE_MAP.get(this));
282   }
283 
isPrimitiveType(TypeKind typeKind)284   private static boolean isPrimitiveType(TypeKind typeKind) {
285     return !typeKind.equals(TypeKind.OBJECT);
286   }
287 
createBoxedTypeMap()288   private static Map<TypeNode, TypeNode> createBoxedTypeMap() {
289     Map<TypeNode, TypeNode> map = new HashMap<>();
290     map.put(TypeNode.INT, TypeNode.INT_OBJECT);
291     map.put(TypeNode.BOOLEAN, TypeNode.BOOLEAN_OBJECT);
292     map.put(TypeNode.BYTE, TypeNode.BYTE_OBJECT);
293     map.put(TypeNode.CHAR, TypeNode.CHAR_OBJECT);
294     map.put(TypeNode.FLOAT, TypeNode.FLOAT_OBJECT);
295     map.put(TypeNode.LONG, TypeNode.LONG_OBJECT);
296     map.put(TypeNode.SHORT, TypeNode.SHORT_OBJECT);
297     map.put(TypeNode.DOUBLE, TypeNode.DOUBLE_OBJECT);
298     return map;
299   }
300 }
301