/*
* 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");
}
};
}