1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *   https://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.enterprise.connectedapps.processor;
17 
18 import static com.google.android.enterprise.connectedapps.processor.CommonClassNames.BUNDLER_TYPE_CLASSNAME;
19 import static java.util.stream.Collectors.toList;
20 
21 import com.squareup.javapoet.ClassName;
22 import com.squareup.javapoet.CodeBlock;
23 import java.util.ArrayList;
24 import java.util.List;
25 import javax.lang.model.type.ArrayType;
26 import javax.lang.model.type.DeclaredType;
27 import javax.lang.model.type.TypeMirror;
28 
29 /** Utilities for manipulating {@link TypeMirror} instances. */
30 public class TypeUtils {
31 
isArray(TypeMirror type)32   public static boolean isArray(TypeMirror type) {
33     return type instanceof ArrayType;
34   }
35 
36   /**
37    * Extract a type from an array.
38    *
39    * <p>Assumes that {@code type} represents an array.
40    */
extractTypeFromArray(TypeMirror type)41   public static TypeMirror extractTypeFromArray(TypeMirror type) {
42     return ((ArrayType) type).getComponentType();
43   }
44 
isGeneric(TypeMirror type)45   public static boolean isGeneric(TypeMirror type) {
46     if (type instanceof DeclaredType) {
47       return !((DeclaredType) type).getTypeArguments().isEmpty();
48     }
49     return false;
50   }
51 
removeTypeArguments(TypeMirror type)52   public static TypeMirror removeTypeArguments(TypeMirror type) {
53     if (type instanceof DeclaredType) {
54       return ((DeclaredType) type).asElement().asType();
55     }
56     return type;
57   }
58 
extractTypeArguments(TypeMirror type)59   public static List<TypeMirror> extractTypeArguments(TypeMirror type) {
60     if (!(type instanceof DeclaredType)) {
61       return null;
62     }
63 
64     return new ArrayList<>(((DeclaredType) type).getTypeArguments());
65   }
66 
getRawTypeClassName(TypeMirror type)67   static ClassName getRawTypeClassName(TypeMirror type) {
68     String rawTypeQualifiedName = getRawTypeQualifiedName(type);
69 
70     if (!rawTypeQualifiedName.contains(".")) {
71       return ClassName.get("", rawTypeQualifiedName);
72     }
73 
74     String packageName = rawTypeQualifiedName.substring(0, rawTypeQualifiedName.lastIndexOf("."));
75     String simpleName = rawTypeQualifiedName.substring(rawTypeQualifiedName.lastIndexOf(".") + 1);
76 
77     return ClassName.get(packageName, simpleName);
78   }
79 
getRawTypeQualifiedName(TypeMirror type)80   static String getRawTypeQualifiedName(TypeMirror type) {
81     // This converts e.g. java.util.List<String> into java.util.List
82     return type.toString().split("<", 2)[0];
83   }
84 
generateBundlerType(TypeMirror type)85   static CodeBlock generateBundlerType(TypeMirror type) {
86     if (isArray(type)) {
87       return generateArrayBundlerType(type);
88     }
89     if (isGeneric(type)) {
90       return generateGenericBundlerType(type);
91     }
92     return CodeBlock.of("$T.of($S)", BUNDLER_TYPE_CLASSNAME, getRawTypeQualifiedName(type));
93   }
94 
getArrayRootType(TypeMirror type)95   static TypeMirror getArrayRootType(TypeMirror type) {
96     while (TypeUtils.isArray(type)) {
97       type = TypeUtils.extractTypeFromArray(type);
98     }
99 
100     return type;
101   }
102 
isPrimitiveArray(TypeMirror type)103   static boolean isPrimitiveArray(TypeMirror type) {
104     return getArrayRootType(type).getKind().isPrimitive();
105   }
106 
generateArrayBundlerType(TypeMirror type)107   private static CodeBlock generateArrayBundlerType(TypeMirror type) {
108     TypeMirror arrayType = extractTypeFromArray(type);
109 
110     if (isPrimitiveArray(arrayType)) {
111       return CodeBlock.of("$T.of($S)", BUNDLER_TYPE_CLASSNAME, type);
112     }
113 
114     return CodeBlock.of(
115         "$T.of($S, $L)",
116         BUNDLER_TYPE_CLASSNAME,
117         "java.lang.Object[]",
118         generateBundlerType(arrayType));
119   }
120 
generateGenericBundlerType(TypeMirror type)121   private static CodeBlock generateGenericBundlerType(TypeMirror type) {
122     CodeBlock.Builder typeArgs = CodeBlock.builder();
123 
124     List<CodeBlock> typeArgBlocks =
125         extractTypeArguments(type).stream().map(TypeUtils::generateBundlerType).collect(toList());
126 
127     typeArgs.add(typeArgBlocks.get(0));
128     for (CodeBlock typeArgBlock : typeArgBlocks.subList(1, typeArgBlocks.size())) {
129       typeArgs.add(", $L", typeArgBlock);
130     }
131 
132     return CodeBlock.of(
133         "$T.of($S, $L)", BUNDLER_TYPE_CLASSNAME, getRawTypeQualifiedName(type), typeArgs.build());
134   }
135 
TypeUtils()136   private TypeUtils() {}
137 }
138