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