LayoutXmlProcessor.java revision 0cb9fbb96197af013f4f879ed6cddf2681b88fd6
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 com.google.common.base.Predicate; 17import com.google.common.collect.Iterables; 18 19import org.apache.commons.lang3.StringEscapeUtils; 20import org.xml.sax.SAXException; 21 22import android.databinding.BindingBuildInfo; 23import android.databinding.tool.store.LayoutFileParser; 24import android.databinding.tool.store.ResourceBundle; 25import android.databinding.tool.writer.JavaFileWriter; 26 27import java.io.File; 28import java.io.FilenameFilter; 29import java.io.IOException; 30import java.io.StringWriter; 31import java.util.ArrayList; 32import java.util.List; 33import java.util.UUID; 34 35import javax.xml.bind.JAXBContext; 36import javax.xml.bind.JAXBException; 37import javax.xml.bind.Marshaller; 38import javax.xml.parsers.ParserConfigurationException; 39import javax.xml.xpath.XPathExpressionException; 40 41/** 42 * Processes the layout XML, stripping the binding attributes and elements 43 * and writes the information into an annotated class file for the annotation 44 * processor to work with. 45 */ 46public class LayoutXmlProcessor { 47 // hardcoded in baseAdapters 48 public static final String RESOURCE_BUNDLE_PACKAGE = "android.databinding.layouts"; 49 public static final String CLASS_NAME = "DataBindingInfo"; 50 private final JavaFileWriter mFileWriter; 51 private final ResourceBundle mResourceBundle; 52 private final int mMinSdk; 53 54 private boolean mProcessingComplete; 55 private boolean mWritten; 56 private final boolean mIsLibrary; 57 private final String mBuildId = UUID.randomUUID().toString(); 58 // can be a list of xml files or folders that contain XML files 59 private final List<File> mResources; 60 61 public LayoutXmlProcessor(String applicationPackage, List<File> resources, 62 JavaFileWriter fileWriter, int minSdk, boolean isLibrary) { 63 mFileWriter = fileWriter; 64 mResourceBundle = new ResourceBundle(applicationPackage); 65 mResources = resources; 66 mMinSdk = minSdk; 67 mIsLibrary = isLibrary; 68 } 69 70 public static List<File> getLayoutFiles(List<File> resources) { 71 List<File> result = new ArrayList<File>(); 72 for (File resource : Iterables.filter(resources, fileExists)) { 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 Predicate<File> fileExists = new Predicate<File>() { 191 @Override 192 public boolean apply(File input) { 193 return input.exists() && input.canRead(); 194 } 195 }; 196 197 private static final FilenameFilter layoutFolderFilter = new FilenameFilter() { 198 @Override 199 public boolean accept(File dir, String name) { 200 return name.startsWith("layout"); 201 } 202 }; 203 204 private static final FilenameFilter xmlFileFilter = new FilenameFilter() { 205 @Override 206 public boolean accept(File dir, String name) { 207 return name.toLowerCase().endsWith(".xml"); 208 } 209 }; 210} 211