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