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