ProcessExpressions.java revision b6887f1479c3ecec38a7989748ef33de1fbcd973
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
17package android.databinding.annotationprocessor;
18
19import com.google.common.base.Preconditions;
20
21import org.apache.commons.io.FileUtils;
22import org.apache.commons.io.IOUtils;
23import org.apache.commons.lang3.StringUtils;
24
25import android.databinding.BindingBuildInfo;
26import android.databinding.tool.CompilerChef;
27import android.databinding.tool.reflection.SdkUtil;
28import android.databinding.tool.store.ResourceBundle;
29import android.databinding.tool.util.GenerationalClassUtil;
30import android.databinding.tool.util.L;
31
32import java.io.File;
33import java.io.FilenameFilter;
34import java.io.IOException;
35import java.io.InputStream;
36import java.io.Serializable;
37import java.util.HashMap;
38import java.util.List;
39import java.util.Map;
40import java.util.Set;
41
42import javax.annotation.processing.ProcessingEnvironment;
43import javax.annotation.processing.RoundEnvironment;
44import javax.xml.bind.JAXBContext;
45import javax.xml.bind.JAXBException;
46import javax.xml.bind.Unmarshaller;
47
48public class ProcessExpressions extends ProcessDataBinding.ProcessingStep {
49
50    private static final String LAYOUT_INFO_FILE_SUFFIX = "-layoutinfo.bin";
51
52    private final ProcessBindable mProcessBindable;
53
54    public ProcessExpressions(ProcessBindable processBindable) {
55        mProcessBindable = processBindable;
56    }
57
58
59    @Override
60    public boolean onHandleStep(RoundEnvironment roundEnvironment,
61            ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
62        ResourceBundle resourceBundle;
63        SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot()));
64        resourceBundle = new ResourceBundle(buildInfo.modulePackage());
65        List<Intermediate> intermediateList =
66                GenerationalClassUtil.loadObjects(getClass().getClassLoader(),
67                        new GenerationalClassUtil.ExtensionFilter(LAYOUT_INFO_FILE_SUFFIX));
68        IntermediateV1 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir());
69        if (mine != null) {
70            mine.removeOverridden(intermediateList);
71            intermediateList.add(mine);
72            saveIntermediate(processingEnvironment, buildInfo, mine);
73        }
74        // generate them here so that bindable parser can read
75        try {
76            generateBinders(resourceBundle, buildInfo, intermediateList);
77        } catch (Throwable t) {
78            L.e(t, "cannot generate view binders");
79        }
80        return true;
81    }
82
83    private void saveIntermediate(ProcessingEnvironment processingEnvironment,
84            BindingBuildInfo buildInfo, IntermediateV1 intermediate) {
85        GenerationalClassUtil.writeIntermediateFile(processingEnvironment,
86                buildInfo.modulePackage(), buildInfo.modulePackage() + LAYOUT_INFO_FILE_SUFFIX,
87                intermediate);
88    }
89
90    @Override
91    public void onProcessingOver(RoundEnvironment roundEnvironment,
92            ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) {
93    }
94
95    private void generateBinders(ResourceBundle resourceBundle, BindingBuildInfo buildInfo,
96            List<Intermediate> intermediates)
97            throws Throwable {
98        for (Intermediate intermediate : intermediates) {
99            intermediate.appendTo(resourceBundle);
100        }
101        writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk(),
102                buildInfo.exportClassListTo());
103    }
104
105    private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) {
106        final File layoutInfoFolder = new File(layoutInfoFolderPath);
107        if (!layoutInfoFolder.isDirectory()) {
108            L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath);
109            return null;
110        }
111        IntermediateV1 result = new IntermediateV1();
112        for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() {
113            @Override
114            public boolean accept(File dir, String name) {
115                return name.endsWith(".xml");
116            }
117        })) {
118            try {
119                result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile));
120            } catch (IOException e) {
121                L.e(e, "cannot load layout file information. Try a clean build");
122            }
123        }
124        return result;
125    }
126
127    private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule,
128            int minSdk, String exportClassNamesTo)
129            throws JAXBException {
130        CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter());
131        if (compilerChef.hasAnythingToGenerate()) {
132            compilerChef.addBRVariables(mProcessBindable);
133            compilerChef.writeViewBinderInterfaces(forLibraryModule);
134            if (!forLibraryModule) {
135                compilerChef.writeViewBinders(minSdk);
136            }
137        }
138        if (forLibraryModule && exportClassNamesTo == null) {
139            L.e("When compiling a library module, build info must include exportClassListTo path");
140        }
141        if (forLibraryModule) {
142            Set<String> classNames = compilerChef.getWrittenClassNames();
143            String out = StringUtils.join(classNames, System.getProperty("line.separator"));
144
145            L.d("Writing list of classes to %s . \nList:%s", exportClassNamesTo, out);
146            try {
147                FileUtils.write(new File(exportClassNamesTo),
148                        out);
149            } catch (IOException e) {
150                L.e(e, "Cannot create list of written classes");
151            }
152        } else {
153            compilerChef.writeDbrFile(minSdk);
154        }
155    }
156
157    public static interface Intermediate extends Serializable {
158
159        Intermediate upgrade();
160
161        public void appendTo(ResourceBundle resourceBundle) throws Throwable;
162    }
163
164    public static class IntermediateV1 implements Intermediate {
165
166        transient Unmarshaller mUnmarshaller;
167
168        // name to xml content map
169        Map<String, String> mLayoutInfoMap = new HashMap<String, String>();
170
171        @Override
172        public Intermediate upgrade() {
173            return this;
174        }
175
176        @Override
177        public void appendTo(ResourceBundle resourceBundle) throws JAXBException {
178            if (mUnmarshaller == null) {
179                JAXBContext context = JAXBContext
180                        .newInstance(ResourceBundle.LayoutFileBundle.class);
181                mUnmarshaller = context.createUnmarshaller();
182            }
183            for (String content : mLayoutInfoMap.values()) {
184                final InputStream is = IOUtils.toInputStream(content);
185                try {
186                    final ResourceBundle.LayoutFileBundle bundle
187                            = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is);
188                    resourceBundle.addLayoutBundle(bundle);
189                    L.d("loaded layout info file %s", bundle);
190                } finally {
191                    IOUtils.closeQuietly(is);
192                }
193            }
194        }
195
196        public void addEntry(String name, String contents) {
197            mLayoutInfoMap.put(name, contents);
198        }
199
200        public void removeOverridden(List<Intermediate> existing) {
201            // this is the way we get rid of files that are copied from previous modules
202            // it is important to do this before saving the intermediate file
203            for (Intermediate old : existing) {
204                if (old instanceof IntermediateV1) {
205                    IntermediateV1 other = (IntermediateV1) old;
206                    for (String key : other.mLayoutInfoMap.keySet()) {
207                        // TODO we should consider the original file as the key here
208                        // but aapt probably cannot provide that information
209                        if (mLayoutInfoMap.remove(key) != null) {
210                            L.d("removing %s from bundle because it came from another module", key);
211                        }
212                    }
213                }
214            }
215        }
216    }
217}
218