1 /*
2  * Copyright (C) 2018 The Android Open Source Project
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  *      http://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 
17 package com.android.settingslib.search;
18 
19 import com.squareup.javapoet.ClassName;
20 import com.squareup.javapoet.FieldSpec;
21 import com.squareup.javapoet.JavaFile;
22 import com.squareup.javapoet.MethodSpec;
23 import com.squareup.javapoet.ParameterizedTypeName;
24 import com.squareup.javapoet.TypeSpec;
25 
26 import java.io.IOException;
27 import java.util.Collection;
28 import java.util.HashSet;
29 import java.util.Set;
30 
31 import javax.annotation.processing.AbstractProcessor;
32 import javax.annotation.processing.Filer;
33 import javax.annotation.processing.Messager;
34 import javax.annotation.processing.ProcessingEnvironment;
35 import javax.annotation.processing.RoundEnvironment;
36 import javax.annotation.processing.SupportedAnnotationTypes;
37 import javax.annotation.processing.SupportedSourceVersion;
38 import javax.lang.model.SourceVersion;
39 import javax.lang.model.element.Element;
40 import javax.lang.model.element.Modifier;
41 import javax.lang.model.element.Name;
42 import javax.lang.model.element.TypeElement;
43 import javax.lang.model.util.SimpleElementVisitor8;
44 import javax.tools.Diagnostic.Kind;
45 
46 /**
47  * Annotation processor for {@link SearchIndexable} that generates {@link SearchIndexableResources}
48  * subclasses.
49  */
50 @SupportedSourceVersion(SourceVersion.RELEASE_8)
51 @SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
52 public class IndexableProcessor extends AbstractProcessor {
53 
54     private static final String PACKAGE = "com.android.settingslib.search";
55     private static final String CLASS_BASE = "SearchIndexableResourcesBase";
56     private static final String CLASS_MOBILE = "SearchIndexableResourcesMobile";
57     private static final String CLASS_TV = "SearchIndexableResourcesTv";
58     private static final String CLASS_WEAR = "SearchIndexableResourcesWear";
59     private static final String CLASS_AUTO = "SearchIndexableResourcesAuto";
60     private static final String CLASS_ARC = "SearchIndexableResourcesArc";
61 
62     private Filer mFiler;
63     private Messager mMessager;
64     private boolean mRanOnce;
65 
66     @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment)67     public boolean process(Set<? extends TypeElement> annotations,
68             RoundEnvironment roundEnvironment) {
69         if (mRanOnce) {
70             // Will get called once per round, but we only want to run on the first one.
71             return true;
72         }
73         mRanOnce = true;
74 
75         final ClassName searchIndexableData = ClassName.get(PACKAGE, "SearchIndexableData");
76 
77         final FieldSpec providers = FieldSpec.builder(
78                 ParameterizedTypeName.get(
79                         ClassName.get(Set.class),
80                         searchIndexableData),
81                 "mProviders",
82                 Modifier.PRIVATE, Modifier.FINAL)
83                 .initializer("new $T()", HashSet.class)
84                 .build();
85 
86         final MethodSpec addIndex = MethodSpec.methodBuilder("addIndex")
87                 .addModifiers(Modifier.PUBLIC)
88                 .addParameter(searchIndexableData, "indexClass")
89                 .addCode("$N.add(indexClass);\n", providers)
90                 .build();
91 
92         final MethodSpec.Builder baseConstructorBuilder = MethodSpec.constructorBuilder()
93                 .addModifiers(Modifier.PUBLIC);
94         final MethodSpec.Builder mobileConstructorBuilder = MethodSpec.constructorBuilder()
95                 .addModifiers(Modifier.PUBLIC);
96         final MethodSpec.Builder tvConstructorBuilder = MethodSpec.constructorBuilder()
97                 .addModifiers(Modifier.PUBLIC);
98         final MethodSpec.Builder wearConstructorBuilder = MethodSpec.constructorBuilder()
99                 .addModifiers(Modifier.PUBLIC);
100         final MethodSpec.Builder autoConstructorBuilder = MethodSpec.constructorBuilder()
101                 .addModifiers(Modifier.PUBLIC);
102         final MethodSpec.Builder arcConstructorBuilder = MethodSpec.constructorBuilder()
103                 .addModifiers(Modifier.PUBLIC);
104 
105         for (Element element : roundEnvironment.getElementsAnnotatedWith(SearchIndexable.class)) {
106             if (element.getKind().isClass()) {
107                 Name className = element.accept(new SimpleElementVisitor8<Name, Void>() {
108                     @Override
109                     public Name visitType(TypeElement typeElement, Void aVoid) {
110                         return typeElement.getQualifiedName();
111                     }
112                 }, null);
113                 if (className != null) {
114                     SearchIndexable searchIndexable = element.getAnnotation(SearchIndexable.class);
115 
116                     int forTarget = searchIndexable.forTarget();
117                     MethodSpec.Builder builder = baseConstructorBuilder;
118 
119                     if (forTarget == SearchIndexable.ALL) {
120                         builder = baseConstructorBuilder;
121                     } else if ((forTarget & SearchIndexable.MOBILE) != 0) {
122                         builder = mobileConstructorBuilder;
123                     } else if ((forTarget & SearchIndexable.TV) != 0) {
124                         builder = tvConstructorBuilder;
125                     } else if ((forTarget & SearchIndexable.WEAR) != 0) {
126                         builder = wearConstructorBuilder;
127                     } else if ((forTarget & SearchIndexable.AUTO) != 0) {
128                         builder = autoConstructorBuilder;
129                     } else if ((forTarget & SearchIndexable.ARC) != 0) {
130                         builder = arcConstructorBuilder;
131                     }
132                     builder.addCode(
133                             "$N(new SearchIndexableData($L.class, $L"
134                                     + ".SEARCH_INDEX_DATA_PROVIDER));\n",
135                             addIndex, className, className);
136                 } else {
137                     throw new IllegalStateException("Null classname from " + element);
138                 }
139             }
140         }
141 
142         final MethodSpec getProviderValues = MethodSpec.methodBuilder("getProviderValues")
143                 .addAnnotation(Override.class)
144                 .addModifiers(Modifier.PUBLIC)
145                 .returns(ParameterizedTypeName.get(
146                         ClassName.get(Collection.class),
147                         searchIndexableData))
148                 .addCode("return $N;\n", providers)
149                 .build();
150 
151         final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
152                 .addModifiers(Modifier.PUBLIC)
153                 .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
154                 .addField(providers)
155                 .addMethod(baseConstructorBuilder.build())
156                 .addMethod(addIndex)
157                 .addMethod(getProviderValues)
158                 .build();
159         final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();
160 
161         final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
162                 TypeSpec.classBuilder(CLASS_MOBILE)
163                         .addModifiers(Modifier.PUBLIC)
164                         .superclass(ClassName.get(PACKAGE, baseClass.name))
165                         .addMethod(mobileConstructorBuilder.build())
166                         .build())
167                 .build();
168 
169         final JavaFile searchIndexableResourcesTv = JavaFile.builder(PACKAGE,
170                 TypeSpec.classBuilder(CLASS_TV)
171                         .addModifiers(Modifier.PUBLIC)
172                         .superclass(ClassName.get(PACKAGE, baseClass.name))
173                         .addMethod(tvConstructorBuilder.build())
174                         .build())
175                 .build();
176 
177         final JavaFile searchIndexableResourcesWear = JavaFile.builder(PACKAGE,
178                 TypeSpec.classBuilder(CLASS_WEAR)
179                         .addModifiers(Modifier.PUBLIC)
180                         .superclass(ClassName.get(PACKAGE, baseClass.name))
181                         .addMethod(wearConstructorBuilder.build())
182                         .build())
183                 .build();
184 
185         final JavaFile searchIndexableResourcesAuto = JavaFile.builder(PACKAGE,
186                 TypeSpec.classBuilder(CLASS_AUTO)
187                         .addModifiers(Modifier.PUBLIC)
188                         .superclass(ClassName.get(PACKAGE, baseClass.name))
189                         .addMethod(autoConstructorBuilder.build())
190                         .build())
191                 .build();
192 
193         final JavaFile searchIndexableResourcesArc = JavaFile.builder(PACKAGE,
194                 TypeSpec.classBuilder(CLASS_ARC)
195                         .addModifiers(Modifier.PUBLIC)
196                         .superclass(ClassName.get(PACKAGE, baseClass.name))
197                         .addMethod(arcConstructorBuilder.build())
198                         .build())
199                 .build();
200 
201         try {
202             searchIndexableResourcesBase.writeTo(mFiler);
203             searchIndexableResourcesMobile.writeTo(mFiler);
204             searchIndexableResourcesTv.writeTo(mFiler);
205             searchIndexableResourcesWear.writeTo(mFiler);
206             searchIndexableResourcesAuto.writeTo(mFiler);
207             searchIndexableResourcesArc.writeTo(mFiler);
208         } catch (IOException e) {
209             mMessager.printMessage(Kind.ERROR, "Error while writing file: " + e);
210         }
211         return true;
212     }
213 
214     @Override
init(ProcessingEnvironment processingEnvironment)215     public synchronized void init(ProcessingEnvironment processingEnvironment) {
216         super.init(processingEnvironment);
217         mFiler = processingEnvironment.getFiler();
218         mMessager = processingEnvironment.getMessager();
219     }
220 }