• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
14 package android.databinding.tool;
15 
16 import org.apache.commons.lang3.StringEscapeUtils;
17 import org.xml.sax.SAXException;
18 
19 import android.databinding.BindingBuildInfo;
20 import android.databinding.tool.store.LayoutFileParser;
21 import android.databinding.tool.store.ResourceBundle;
22 import android.databinding.tool.writer.JavaFileWriter;
23 
24 import java.io.File;
25 import java.io.FilenameFilter;
26 import java.io.IOException;
27 import java.io.StringWriter;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.UUID;
31 
32 import javax.xml.bind.JAXBContext;
33 import javax.xml.bind.JAXBException;
34 import javax.xml.bind.Marshaller;
35 import javax.xml.parsers.ParserConfigurationException;
36 import 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  */
43 public 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 
LayoutXmlProcessor(String applicationPackage, List<File> resources, JavaFileWriter fileWriter, int minSdk, boolean isLibrary)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 
getLayoutFiles(List<File> resources)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      */
getResourceBundle()90     public ResourceBundle getResourceBundle() {
91         return mResourceBundle;
92     }
93 
processResources(int minSdk)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 
writeLayoutInfoFiles(File xmlOutDir)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 
writeXmlFile(File xmlOutDir, ResourceBundle.LayoutFileBundle layout, Marshaller marshaller)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 
getInfoClassFullName()135     public String getInfoClassFullName() {
136         return RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME;
137     }
138 
toXML(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller)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      */
generateExportFileName(ResourceBundle.LayoutFileBundle layout)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 
writeInfoClass( File sdkDir, File xmlOutDir, File exportClassListTo)164     public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir,
165             /*Nullable*/ File exportClassListTo) {
166         writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false, false);
167     }
168 
writeInfoClass( File sdkDir, File xmlOutDir, File exportClassListTo, boolean enableDebugLogs, boolean printEncodedErrorLogs)169     public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, File exportClassListTo,
170             boolean enableDebugLogs, boolean printEncodedErrorLogs) {
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 + "," +
186                 "printEncodedError=" + printEncodedErrorLogs + ")\n" +
187                 "public class " + CLASS_NAME + " {}\n";
188         mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString);
189     }
190 
191     private static final FilenameFilter layoutFolderFilter = new FilenameFilter() {
192         @Override
193         public boolean accept(File dir, String name) {
194             return name.startsWith("layout");
195         }
196     };
197 
198     private static final FilenameFilter xmlFileFilter = new FilenameFilter() {
199         @Override
200         public boolean accept(File dir, String name) {
201             return name.toLowerCase().endsWith(".xml");
202         }
203     };
204 }
205