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