LayoutXmlProcessor.java revision 890b4850c628f04eb75397e427ba7074e4f9c386
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *      http://www.apache.org/licenses/LICENSE-2.0
7 * Unless required by applicable law or agreed to in writing, software
8 * distributed under the License is distributed on an "AS IS" BASIS,
9 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10 * See the License for the specific language governing permissions and
11 * limitations under the License.
12 */
13
14package android.databinding.tool;
15
16import org.apache.commons.lang3.StringEscapeUtils;
17import org.xml.sax.SAXException;
18
19import android.databinding.BindingBuildInfo;
20import android.databinding.tool.store.LayoutFileParser;
21import android.databinding.tool.store.ResourceBundle;
22import android.databinding.tool.writer.JavaFileWriter;
23
24import java.io.File;
25import java.io.FilenameFilter;
26import java.io.IOException;
27import java.io.StringWriter;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.UUID;
31
32import javax.xml.bind.JAXBContext;
33import javax.xml.bind.JAXBException;
34import javax.xml.bind.Marshaller;
35import javax.xml.parsers.ParserConfigurationException;
36import javax.xml.xpath.XPathExpressionException;
37
38/**
39 * Processes the layout XML, stripping the binding attributes and elements
40 * and writes the information into an annotated class file for the annotation
41 * processor to work with.
42 */
43public class LayoutXmlProcessor {
44    // hardcoded in baseAdapters
45    public static final String RESOURCE_BUNDLE_PACKAGE = "android.databinding.layouts";
46    public static final String CLASS_NAME = "DataBindingInfo";
47    private final JavaFileWriter mFileWriter;
48    private final ResourceBundle mResourceBundle;
49    private final int mMinSdk;
50
51    private boolean mProcessingComplete;
52    private boolean mWritten;
53    private final boolean mIsLibrary;
54    private final String mBuildId = UUID.randomUUID().toString();
55    // can be a list of xml files or folders that contain XML files
56    private final List<File> mResources;
57    private OriginalFileLookup mOriginalFileLookup;
58
59    public LayoutXmlProcessor(String applicationPackage, List<File> resources,
60            JavaFileWriter fileWriter, int minSdk, boolean isLibrary) {
61        mFileWriter = fileWriter;
62        mResourceBundle = new ResourceBundle(applicationPackage);
63        mResources = resources;
64        mMinSdk = minSdk;
65        mIsLibrary = isLibrary;
66    }
67
68    public void setOriginalFileLookup(OriginalFileLookup originalFileLookup) {
69        mOriginalFileLookup = originalFileLookup;
70    }
71
72    public static List<File> getLayoutFiles(List<File> resources) {
73        List<File> result = new ArrayList<File>();
74        for (File resource : resources) {
75            if (!resource.exists() || !resource.canRead()) {
76                continue;
77            }
78            if (resource.isDirectory()) {
79                for (File layoutFolder : resource.listFiles(layoutFolderFilter)) {
80                    for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) {
81                        result.add(xmlFile);
82                    }
83
84                }
85            } else if (xmlFileFilter.accept(resource.getParentFile(), resource.getName())) {
86                result.add(resource);
87            }
88        }
89        return result;
90    }
91
92    /**
93     * used by the studio plugin
94     */
95    public ResourceBundle getResourceBundle() {
96        return mResourceBundle;
97    }
98
99    public boolean processResources(int minSdk)
100            throws ParserConfigurationException, SAXException, XPathExpressionException,
101            IOException {
102        if (mProcessingComplete) {
103            return false;
104        }
105        LayoutFileParser layoutFileParser = new LayoutFileParser();
106        for (File xmlFile : getLayoutFiles(mResources)) {
107            final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser
108                    .parseXml(xmlFile, mResourceBundle.getAppPackage(), mOriginalFileLookup);
109            if (bindingLayout != null && !bindingLayout.isEmpty()) {
110                mResourceBundle.addLayoutBundle(bindingLayout);
111            }
112        }
113        mProcessingComplete = true;
114        return true;
115    }
116
117    public void writeLayoutInfoFiles(File xmlOutDir) throws JAXBException {
118        if (mWritten) {
119            return;
120        }
121        JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
122        Marshaller marshaller = context.createMarshaller();
123
124        for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
125                .values()) {
126            for (ResourceBundle.LayoutFileBundle layout : layouts) {
127                writeXmlFile(xmlOutDir, layout, marshaller);
128            }
129        }
130        mWritten = true;
131    }
132
133    private void writeXmlFile(File xmlOutDir, ResourceBundle.LayoutFileBundle layout,
134            Marshaller marshaller) throws JAXBException {
135        String filename = generateExportFileName(layout) + ".xml";
136        String xml = toXML(layout, marshaller);
137        mFileWriter.writeToFile(new File(xmlOutDir, filename), xml);
138    }
139
140    public String getInfoClassFullName() {
141        return RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME;
142    }
143
144    private String toXML(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller)
145            throws JAXBException {
146        StringWriter writer = new StringWriter();
147        marshaller.marshal(layout, writer);
148        return writer.getBuffer().toString();
149    }
150
151    /**
152     * Generates a string identifier that can uniquely identify the given layout bundle.
153     * This identifier can be used when we need to export data about this layout bundle.
154     */
155    public String generateExportFileName(ResourceBundle.LayoutFileBundle layout) {
156        StringBuilder name = new StringBuilder(layout.getFileName());
157        name.append('-').append(layout.getDirectory());
158        for (int i = name.length() - 1; i >= 0; i--) {
159            char c = name.charAt(i);
160            if (c == '-') {
161                name.deleteCharAt(i);
162                c = Character.toUpperCase(name.charAt(i));
163                name.setCharAt(i, c);
164            }
165        }
166        return name.toString();
167    }
168
169    public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir,
170            /*Nullable*/ File exportClassListTo) {
171        writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false, false);
172    }
173
174    public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, File exportClassListTo,
175            boolean enableDebugLogs, boolean printEncodedErrorLogs) {
176        final String sdkPath = sdkDir == null ? null : StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath());
177        final Class annotation = BindingBuildInfo.class;
178        final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath());
179        final String exportClassListToPath = exportClassListTo == null ? "" :
180                StringEscapeUtils.escapeJava(exportClassListTo.getAbsolutePath());
181        String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" +
182                "import " + annotation.getCanonicalName() + ";\n\n" +
183                "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\", " +
184                "modulePackage=\"" + mResourceBundle.getAppPackage() + "\", " +
185                "sdkRoot=" + "\"" + (sdkPath == null ? "" : sdkPath) + "\"," +
186                "layoutInfoDir=\"" + layoutInfoPath + "\"," +
187                "exportClassListTo=\"" + exportClassListToPath + "\"," +
188                "isLibrary=" + mIsLibrary + "," +
189                "minSdk=" + mMinSdk + "," +
190                "enableDebugLogs=" + enableDebugLogs + "," +
191                "printEncodedError=" + printEncodedErrorLogs + ")\n" +
192                "public class " + CLASS_NAME + " {}\n";
193        mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString);
194    }
195
196    private static final FilenameFilter layoutFolderFilter = new FilenameFilter() {
197        @Override
198        public boolean accept(File dir, String name) {
199            return name.startsWith("layout");
200        }
201    };
202
203    private static final FilenameFilter xmlFileFilter = new FilenameFilter() {
204        @Override
205        public boolean accept(File dir, String name) {
206            return name.toLowerCase().endsWith(".xml");
207        }
208    };
209
210    /**
211     * Helper interface that can find the original copy of a resource XML.
212     */
213    public interface OriginalFileLookup {
214
215        /**
216         * @param file The intermediate build file
217         * @return The original file or null if original File cannot be found.
218         */
219        File getOriginalFileFor(File file);
220    }
221}
222