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