LayoutXmlProcessor.java revision 0c2ed0cbaee2f206e926bfc780b05e9f1e52b551
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
58    public LayoutXmlProcessor(String applicationPackage, List<File> resources,
59            JavaFileWriter fileWriter, int minSdk, boolean isLibrary) {
60        mFileWriter = fileWriter;
61        mResourceBundle = new ResourceBundle(applicationPackage);
62        mResources = resources;
63        mMinSdk = minSdk;
64        mIsLibrary = isLibrary;
65    }
66
67    public static List<File> getLayoutFiles(List<File> resources) {
68        List<File> result = new ArrayList<File>();
69        for (File resource : resources) {
70            if (!resource.exists() || !resource.canRead()) {
71                continue;
72            }
73            if (resource.isDirectory()) {
74                for (File layoutFolder : resource.listFiles(layoutFolderFilter)) {
75                    for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) {
76                        result.add(xmlFile);
77                    }
78
79                }
80            } else if (xmlFileFilter.accept(resource.getParentFile(), resource.getName())) {
81                result.add(resource);
82            }
83        }
84        return result;
85    }
86
87    /**
88     * used by the studio plugin
89     */
90    public ResourceBundle getResourceBundle() {
91        return mResourceBundle;
92    }
93
94    public boolean processResources(int minSdk)
95            throws ParserConfigurationException, SAXException, XPathExpressionException,
96            IOException {
97        if (mProcessingComplete) {
98            return false;
99        }
100        LayoutFileParser layoutFileParser = new LayoutFileParser();
101        for (File xmlFile : getLayoutFiles(mResources)) {
102            final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser
103                    .parseXml(xmlFile, mResourceBundle.getAppPackage(), minSdk);
104            if (bindingLayout != null && !bindingLayout.isEmpty()) {
105                mResourceBundle.addLayoutBundle(bindingLayout);
106            }
107        }
108        mProcessingComplete = true;
109        return true;
110    }
111
112    public void writeLayoutInfoFiles(File xmlOutDir) throws JAXBException {
113        if (mWritten) {
114            return;
115        }
116        JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class);
117        Marshaller marshaller = context.createMarshaller();
118
119        for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
120                .values()) {
121            for (ResourceBundle.LayoutFileBundle layout : layouts) {
122                writeXmlFile(xmlOutDir, layout, marshaller);
123            }
124        }
125        mWritten = true;
126    }
127
128    private void writeXmlFile(File xmlOutDir, ResourceBundle.LayoutFileBundle layout,
129            Marshaller marshaller) throws JAXBException {
130        String filename = generateExportFileName(layout) + ".xml";
131        String xml = toXML(layout, marshaller);
132        mFileWriter.writeToFile(new File(xmlOutDir, filename), xml);
133    }
134
135    public String getInfoClassFullName() {
136        return RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME;
137    }
138
139    private String toXML(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller)
140            throws JAXBException {
141        StringWriter writer = new StringWriter();
142        marshaller.marshal(layout, writer);
143        return writer.getBuffer().toString();
144    }
145
146    /**
147     * Generates a string identifier that can uniquely identify the given layout bundle.
148     * This identifier can be used when we need to export data about this layout bundle.
149     */
150    public String generateExportFileName(ResourceBundle.LayoutFileBundle layout) {
151        StringBuilder name = new StringBuilder(layout.getFileName());
152        name.append('-').append(layout.getDirectory());
153        for (int i = name.length() - 1; i >= 0; i--) {
154            char c = name.charAt(i);
155            if (c == '-') {
156                name.deleteCharAt(i);
157                c = Character.toUpperCase(name.charAt(i));
158                name.setCharAt(i, c);
159            }
160        }
161        return name.toString();
162    }
163
164    public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir,
165            /*Nullable*/ File exportClassListTo) {
166        writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false);
167    }
168
169    public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, File exportClassListTo,
170            boolean enableDebugLogs) {
171        final String sdkPath = sdkDir == null ? null : StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath());
172        final Class annotation = BindingBuildInfo.class;
173        final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath());
174        final String exportClassListToPath = exportClassListTo == null ? "" :
175                StringEscapeUtils.escapeJava(exportClassListTo.getAbsolutePath());
176        String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" +
177                "import " + annotation.getCanonicalName() + ";\n\n" +
178                "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\", " +
179                "modulePackage=\"" + mResourceBundle.getAppPackage() + "\", " +
180                "sdkRoot=" + "\"" + (sdkPath == null ? "" : sdkPath) + "\"," +
181                "layoutInfoDir=\"" + layoutInfoPath + "\"," +
182                "exportClassListTo=\"" + exportClassListToPath + "\"," +
183                "isLibrary=" + mIsLibrary + "," +
184                "minSdk=" + mMinSdk + "," +
185                "enableDebugLogs=" + enableDebugLogs + ")\n" +
186                "public class " + CLASS_NAME + " {}\n";
187        mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString);
188    }
189
190    private static final FilenameFilter layoutFolderFilter = new FilenameFilter() {
191        @Override
192        public boolean accept(File dir, String name) {
193            return name.startsWith("layout");
194        }
195    };
196
197    private static final FilenameFilter xmlFileFilter = new FilenameFilter() {
198        @Override
199        public boolean accept(File dir, String name) {
200            return name.toLowerCase().endsWith(".xml");
201        }
202    };
203}
204