1 /*
2  * Copyright (C) 2015 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 android.databinding.annotationprocessor;
18 
19 import android.databinding.BindingBuildInfo;
20 import android.databinding.tool.CompilerChef;
21 import android.databinding.tool.processing.Scope;
22 import android.databinding.tool.reflection.ModelAnalyzer;
23 import android.databinding.tool.util.L;
24 import android.databinding.tool.util.Preconditions;
25 import android.databinding.tool.writer.AnnotationJavaFileWriter;
26 import android.databinding.tool.writer.BRWriter;
27 import android.databinding.tool.writer.JavaFileWriter;
28 
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Set;
32 
33 import javax.annotation.processing.AbstractProcessor;
34 import javax.annotation.processing.ProcessingEnvironment;
35 import javax.annotation.processing.RoundEnvironment;
36 import javax.annotation.processing.SupportedAnnotationTypes;
37 import javax.lang.model.SourceVersion;
38 import javax.lang.model.element.TypeElement;
39 import javax.xml.bind.JAXBException;
40 
41 @SupportedAnnotationTypes({
42         "android.databinding.BindingAdapter",
43         "android.databinding.Untaggable",
44         "android.databinding.BindingMethods",
45         "android.databinding.BindingConversion",
46         "android.databinding.BindingBuildInfo"}
47 )
48 /**
49  * Parent annotation processor that dispatches sub steps to ensure execution order.
50  * Use initProcessingSteps to add a new step.
51  */
52 public class ProcessDataBinding extends AbstractProcessor {
53     private List<ProcessingStep> mProcessingSteps;
54     @Override
process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)55     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
56         if (mProcessingSteps == null) {
57             initProcessingSteps();
58         }
59         final BindingBuildInfo buildInfo = BuildInfoUtil.load(roundEnv);
60         if (buildInfo == null) {
61             return false;
62         }
63         boolean done = true;
64         for (ProcessingStep step : mProcessingSteps) {
65             try {
66                 done = step.runStep(roundEnv, processingEnv, buildInfo) && done;
67             } catch (JAXBException e) {
68                 L.e(e, "Exception while handling step %s", step);
69             }
70         }
71         if (roundEnv.processingOver()) {
72             for (ProcessingStep step : mProcessingSteps) {
73                 step.onProcessingOver(roundEnv, processingEnv, buildInfo);
74             }
75         }
76         Scope.assertNoError();
77         return done;
78     }
79 
80     @Override
getSupportedSourceVersion()81     public SourceVersion getSupportedSourceVersion() {
82         return SourceVersion.latest();
83     }
84 
initProcessingSteps()85     private void initProcessingSteps() {
86         final ProcessBindable processBindable = new ProcessBindable();
87         mProcessingSteps = Arrays.asList(
88                 new ProcessMethodAdapters(),
89                 new ProcessExpressions(),
90                 processBindable
91         );
92         Callback dataBinderWriterCallback = new Callback() {
93             CompilerChef mChef;
94             BRWriter mBRWriter;
95             boolean mLibraryProject;
96             int mMinSdk;
97 
98             @Override
99             public void onChefReady(CompilerChef chef, boolean libraryProject, int minSdk) {
100                 Preconditions.checkNull(mChef, "Cannot set compiler chef twice");
101                 chef.addBRVariables(processBindable);
102                 mChef = chef;
103                 mLibraryProject = libraryProject;
104                 mMinSdk = minSdk;
105                 considerWritingMapper();
106                 mChef.writeDynamicUtil();
107             }
108 
109             private void considerWritingMapper() {
110                 if (mLibraryProject || mChef == null || mBRWriter == null) {
111                     return;
112                 }
113                 mChef.writeDataBinderMapper(mMinSdk, mBRWriter);
114             }
115 
116             @Override
117             public void onBrWriterReady(BRWriter brWriter) {
118                 Preconditions.checkNull(mBRWriter, "Cannot set br writer twice");
119                 mBRWriter = brWriter;
120                 considerWritingMapper();
121             }
122         };
123         AnnotationJavaFileWriter javaFileWriter = new AnnotationJavaFileWriter(processingEnv);
124         for (ProcessingStep step : mProcessingSteps) {
125             step.mJavaFileWriter = javaFileWriter;
126             step.mCallback = dataBinderWriterCallback;
127         }
128     }
129 
130     @Override
init(ProcessingEnvironment processingEnv)131     public synchronized void init(ProcessingEnvironment processingEnv) {
132         super.init(processingEnv);
133         ModelAnalyzer.setProcessingEnvironment(processingEnv);
134     }
135 
136     /**
137      * To ensure execution order and binding build information, we use processing steps.
138      */
139     public abstract static class ProcessingStep {
140         private boolean mDone;
141         private JavaFileWriter mJavaFileWriter;
142         protected Callback mCallback;
143 
getWriter()144         protected JavaFileWriter getWriter() {
145             return mJavaFileWriter;
146         }
147 
runStep(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo)148         private boolean runStep(RoundEnvironment roundEnvironment,
149                 ProcessingEnvironment processingEnvironment,
150                 BindingBuildInfo buildInfo) throws JAXBException {
151             if (mDone) {
152                 return true;
153             }
154             mDone = onHandleStep(roundEnvironment, processingEnvironment, buildInfo);
155             return mDone;
156         }
157 
158         /**
159          * Invoked in each annotation processing step.
160          *
161          * @return True if it is done and should never be invoked again.
162          */
onHandleStep(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo)163         abstract public boolean onHandleStep(RoundEnvironment roundEnvironment,
164                 ProcessingEnvironment processingEnvironment,
165                 BindingBuildInfo buildInfo) throws JAXBException;
166 
167         /**
168          * Invoked when processing is done. A good place to generate the output if the
169          * processor requires multiple steps.
170          */
onProcessingOver(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo)171         abstract public void onProcessingOver(RoundEnvironment roundEnvironment,
172                 ProcessingEnvironment processingEnvironment,
173                 BindingBuildInfo buildInfo);
174     }
175 
176     interface Callback {
onChefReady(CompilerChef chef, boolean libraryProject, int minSdk)177         void onChefReady(CompilerChef chef, boolean libraryProject, int minSdk);
onBrWriterReady(BRWriter brWriter)178         void onBrWriterReady(BRWriter brWriter);
179     }
180 }
181