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