| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.databinding.tool; |
| |
| import org.apache.commons.lang3.StringEscapeUtils; |
| import org.xml.sax.SAXException; |
| |
| import android.databinding.BindingBuildInfo; |
| import android.databinding.tool.store.LayoutFileParser; |
| import android.databinding.tool.store.ResourceBundle; |
| import android.databinding.tool.writer.JavaFileWriter; |
| |
| import java.io.File; |
| import java.io.FilenameFilter; |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.UUID; |
| |
| import javax.xml.bind.JAXBContext; |
| import javax.xml.bind.JAXBException; |
| import javax.xml.bind.Marshaller; |
| import javax.xml.parsers.ParserConfigurationException; |
| import javax.xml.xpath.XPathExpressionException; |
| |
| /** |
| * Processes the layout XML, stripping the binding attributes and elements |
| * and writes the information into an annotated class file for the annotation |
| * processor to work with. |
| */ |
| public class LayoutXmlProcessor { |
| // hardcoded in baseAdapters |
| public static final String RESOURCE_BUNDLE_PACKAGE = "android.databinding.layouts"; |
| public static final String CLASS_NAME = "DataBindingInfo"; |
| private final JavaFileWriter mFileWriter; |
| private final ResourceBundle mResourceBundle; |
| private final int mMinSdk; |
| |
| private boolean mProcessingComplete; |
| private boolean mWritten; |
| private final boolean mIsLibrary; |
| private final String mBuildId = UUID.randomUUID().toString(); |
| // can be a list of xml files or folders that contain XML files |
| private final List<File> mResources; |
| |
| public LayoutXmlProcessor(String applicationPackage, List<File> resources, |
| JavaFileWriter fileWriter, int minSdk, boolean isLibrary) { |
| mFileWriter = fileWriter; |
| mResourceBundle = new ResourceBundle(applicationPackage); |
| mResources = resources; |
| mMinSdk = minSdk; |
| mIsLibrary = isLibrary; |
| } |
| |
| public static List<File> getLayoutFiles(List<File> resources) { |
| List<File> result = new ArrayList<File>(); |
| for (File resource : resources) { |
| if (!resource.exists() || !resource.canRead()) { |
| continue; |
| } |
| if (resource.isDirectory()) { |
| for (File layoutFolder : resource.listFiles(layoutFolderFilter)) { |
| for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) { |
| result.add(xmlFile); |
| } |
| |
| } |
| } else if (xmlFileFilter.accept(resource.getParentFile(), resource.getName())) { |
| result.add(resource); |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * used by the studio plugin |
| */ |
| public ResourceBundle getResourceBundle() { |
| return mResourceBundle; |
| } |
| |
| public boolean processResources(int minSdk) |
| throws ParserConfigurationException, SAXException, XPathExpressionException, |
| IOException { |
| if (mProcessingComplete) { |
| return false; |
| } |
| LayoutFileParser layoutFileParser = new LayoutFileParser(); |
| for (File xmlFile : getLayoutFiles(mResources)) { |
| final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser |
| .parseXml(xmlFile, mResourceBundle.getAppPackage(), minSdk); |
| if (bindingLayout != null && !bindingLayout.isEmpty()) { |
| mResourceBundle.addLayoutBundle(bindingLayout); |
| } |
| } |
| mProcessingComplete = true; |
| return true; |
| } |
| |
| public void writeLayoutInfoFiles(File xmlOutDir) throws JAXBException { |
| if (mWritten) { |
| return; |
| } |
| JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class); |
| Marshaller marshaller = context.createMarshaller(); |
| |
| for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles() |
| .values()) { |
| for (ResourceBundle.LayoutFileBundle layout : layouts) { |
| writeXmlFile(xmlOutDir, layout, marshaller); |
| } |
| } |
| mWritten = true; |
| } |
| |
| private void writeXmlFile(File xmlOutDir, ResourceBundle.LayoutFileBundle layout, |
| Marshaller marshaller) throws JAXBException { |
| String filename = generateExportFileName(layout) + ".xml"; |
| String xml = toXML(layout, marshaller); |
| mFileWriter.writeToFile(new File(xmlOutDir, filename), xml); |
| } |
| |
| public String getInfoClassFullName() { |
| return RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME; |
| } |
| |
| private String toXML(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller) |
| throws JAXBException { |
| StringWriter writer = new StringWriter(); |
| marshaller.marshal(layout, writer); |
| return writer.getBuffer().toString(); |
| } |
| |
| /** |
| * Generates a string identifier that can uniquely identify the given layout bundle. |
| * This identifier can be used when we need to export data about this layout bundle. |
| */ |
| public String generateExportFileName(ResourceBundle.LayoutFileBundle layout) { |
| StringBuilder name = new StringBuilder(layout.getFileName()); |
| name.append('-').append(layout.getDirectory()); |
| for (int i = name.length() - 1; i >= 0; i--) { |
| char c = name.charAt(i); |
| if (c == '-') { |
| name.deleteCharAt(i); |
| c = Character.toUpperCase(name.charAt(i)); |
| name.setCharAt(i, c); |
| } |
| } |
| return name.toString(); |
| } |
| |
| public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, |
| /*Nullable*/ File exportClassListTo) { |
| writeInfoClass(sdkDir, xmlOutDir, exportClassListTo, false, false); |
| } |
| |
| public void writeInfoClass(/*Nullable*/ File sdkDir, File xmlOutDir, File exportClassListTo, |
| boolean enableDebugLogs, boolean printEncodedErrorLogs) { |
| final String sdkPath = sdkDir == null ? null : StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath()); |
| final Class annotation = BindingBuildInfo.class; |
| final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath()); |
| final String exportClassListToPath = exportClassListTo == null ? "" : |
| StringEscapeUtils.escapeJava(exportClassListTo.getAbsolutePath()); |
| String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" + |
| "import " + annotation.getCanonicalName() + ";\n\n" + |
| "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\", " + |
| "modulePackage=\"" + mResourceBundle.getAppPackage() + "\", " + |
| "sdkRoot=" + "\"" + (sdkPath == null ? "" : sdkPath) + "\"," + |
| "layoutInfoDir=\"" + layoutInfoPath + "\"," + |
| "exportClassListTo=\"" + exportClassListToPath + "\"," + |
| "isLibrary=" + mIsLibrary + "," + |
| "minSdk=" + mMinSdk + "," + |
| "enableDebugLogs=" + enableDebugLogs + "," + |
| "printEncodedError=" + printEncodedErrorLogs + ")\n" + |
| "public class " + CLASS_NAME + " {}\n"; |
| mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString); |
| } |
| |
| private static final FilenameFilter layoutFolderFilter = new FilenameFilter() { |
| @Override |
| public boolean accept(File dir, String name) { |
| return name.startsWith("layout"); |
| } |
| }; |
| |
| private static final FilenameFilter xmlFileFilter = new FilenameFilter() { |
| @Override |
| public boolean accept(File dir, String name) { |
| return name.toLowerCase().endsWith(".xml"); |
| } |
| }; |
| } |