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