diff options
Diffstat (limited to 'compiler/src/main/java/com/android/databinding')
10 files changed, 403 insertions, 170 deletions
diff --git a/compiler/src/main/java/com/android/databinding/LayoutBinder.java b/compiler/src/main/java/com/android/databinding/LayoutBinder.java index a285e165..a2aa2485 100644 --- a/compiler/src/main/java/com/android/databinding/LayoutBinder.java +++ b/compiler/src/main/java/com/android/databinding/LayoutBinder.java @@ -43,6 +43,7 @@ public class LayoutBinder { private final ExpressionParser mExpressionParser; private final List<BindingTarget> mBindingTargets; private String mPackage; + private String mModulePackage; private String mProjectPackage; private String mBaseClassName; private final HashMap<String, String> mUserDefinedVariables = new HashMap<String, String>(); @@ -57,7 +58,8 @@ public class LayoutBinder { mBindingTargets = new ArrayList<BindingTarget>(); mBundle = layoutBundle; mProjectPackage = resourceBundle.getAppPackage(); - mPackage = mProjectPackage + ".generated"; + mModulePackage = layoutBundle.getModulePackage(); + mPackage = layoutBundle.getModulePackage() + ".generated"; mBaseClassName = ParserHelper.INSTANCE$.toClassName(layoutBundle.getFileName()) + "Binding"; // copy over data. for (Map.Entry<String, String> variable : mBundle.getVariables().entrySet()) { @@ -159,6 +161,10 @@ public class LayoutBinder { return mPackage; } + public String getModulePackage() { + return mModulePackage; + } + public void setPackage(String aPackage) { mPackage = aPackage; } diff --git a/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java b/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java index ed170bff..d0ce5068 100644 --- a/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java +++ b/compiler/src/main/java/com/android/databinding/LayoutXmlProcessor.java @@ -20,15 +20,16 @@ import com.android.databinding.store.LayoutFileParser; import com.android.databinding.store.ResourceBundle; import com.android.databinding.writer.JavaFileWriter; -import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang3.StringEscapeUtils; import org.xml.sax.SAXException; +import android.binding.BindingBuildInfo; + import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.StringWriter; -import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -44,24 +45,51 @@ import javax.xml.xpath.XPathExpressionException; * processor to work with. */ public class LayoutXmlProcessor { - - public static final String RESOURCE_BUNDLE_PACKAGE = "com.android.databinding.layouts."; - public static final String APPLICATION_INFO_CLASS = "ApplicationBindingInfo"; + // hardcoded in baseAdapters + public static final String RESOURCE_BUNDLE_PACKAGE = "com.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(); - private final List<File> mResourceFolders; + // can be a list of xml files or folders that contain XML files + private final List<File> mResources; - public LayoutXmlProcessor(String applicationPackage, List<File> resourceFolders, - JavaFileWriter fileWriter, int minSdk) { + public LayoutXmlProcessor(String applicationPackage, List<File> resources, + JavaFileWriter fileWriter, int minSdk, boolean isLibrary) { mFileWriter = fileWriter; mResourceBundle = new ResourceBundle(applicationPackage); - mResourceFolders = resourceFolders; + mResources = resources; mMinSdk = minSdk; + mIsLibrary = isLibrary; + } + + public static List<File> getLayoutFiles(List<File> resources) { + List<File> result = new ArrayList<File>(); + for (File resource : Iterables.filter(resources, fileExists)) { + 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() @@ -72,92 +100,101 @@ public class LayoutXmlProcessor { } LayoutFileParser layoutFileParser = new LayoutFileParser(); int layoutId = 0; - for (File resFolder : Iterables.filter(mResourceFolders, fileExists)) { - for (File layoutFolder : resFolder.listFiles(layoutFolderFilter)) { - for (File xmlFile : layoutFolder.listFiles(xmlFileFilter)) { - final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser - .parseXml(xmlFile, mResourceBundle.getAppPackage(), layoutId); - if (bindingLayout != null && !bindingLayout.isEmpty()) { - mResourceBundle.addLayoutBundle(bindingLayout, layoutId); - layoutId++; - } - } + for (File xmlFile : getLayoutFiles(mResources)) { + final ResourceBundle.LayoutFileBundle bindingLayout = layoutFileParser + .parseXml(xmlFile, mResourceBundle.getAppPackage(), layoutId); + if (bindingLayout != null && !bindingLayout.isEmpty()) { + mResourceBundle.addLayoutBundle(bindingLayout, layoutId); + layoutId++; } } mProcessingComplete = true; return true; } - public ResourceBundle getResourceBundle() { - return mResourceBundle; - } - - public void writeIntermediateFile(File sdkDir) throws JAXBException { + public void writeIntermediateFile(File sdkDir, File xmlOutDir) throws JAXBException { if (mWritten) { return; } JAXBContext context = JAXBContext.newInstance(ResourceBundle.LayoutFileBundle.class); Marshaller marshaller = context.createMarshaller(); - writeAppInfo(marshaller, sdkDir); + writeInfoClass(marshaller, sdkDir, xmlOutDir); for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles() .values()) { for (ResourceBundle.LayoutFileBundle layout : layouts) { - writeAnnotatedFile(layout, marshaller); + writeXmlFile(xmlOutDir, layout, marshaller); } } mWritten = true; } - private void writeAnnotatedFile(ResourceBundle.LayoutFileBundle layout, Marshaller marshaller) + 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 { - StringBuilder className = new StringBuilder(layout.getFileName()); - className.append('-').append(layout.getDirectory()); - for (int i = className.length() - 1; i >= 0; i--) { - char c = className.charAt(i); + 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. + */ + private 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 == '-') { - className.deleteCharAt(i); - c = Character.toUpperCase(className.charAt(i)); - className.setCharAt(i, c); + name.deleteCharAt(i); + c = Character.toUpperCase(name.charAt(i)); + name.setCharAt(i, c); } } - className.setCharAt(0, Character.toUpperCase(className.charAt(0))); - StringWriter writer = new StringWriter(); - marshaller.marshal(layout, writer); - String xml = writer.getBuffer().toString(); - String classString = "import android.binding.BinderBundle;\n\n" + - "@BinderBundle(\"" + - Base64.encodeBase64String(xml.getBytes(StandardCharsets.UTF_8)) + - "\")\n" + - "public class " + className + " {}\n"; - mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + className, classString); + return name.toString(); } - private void writeAppInfo(Marshaller marshaller, File sdkDir) { + private void writeInfoClass(Marshaller marshaller, File sdkDir, File xmlOutDir) { final String sdkPath = StringEscapeUtils.escapeJava(sdkDir.getAbsolutePath()); - String classString = "import android.binding.BindingAppInfo;\n\n" + - "@BindingAppInfo(buildId=\"" + mBuildId + "\", " + - "applicationPackage=\"" + mResourceBundle.getAppPackage() + "\", " + + final Class annotation = BindingBuildInfo.class; + final String layoutInfoPath = StringEscapeUtils.escapeJava(xmlOutDir.getAbsolutePath()); + String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" + + "import " + annotation.getCanonicalName() + ";\n\n" + + "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\", " + + "modulePackage=\"" + mResourceBundle.getAppPackage() + "\", " + "sdkRoot=\"" + sdkPath + "\", " + + "layoutInfoDir=\"" + layoutInfoPath + "\"," + + "isLibrary=" + mIsLibrary + "," + "minSdk=" + mMinSdk + ")\n" + - "public class " + APPLICATION_INFO_CLASS + " {}\n"; - mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + APPLICATION_INFO_CLASS, classString); + "public class " + CLASS_NAME + " {}\n"; + mFileWriter.writeToFile(mResourceBundle.getAppPackage() + "." + CLASS_NAME, classString); } - private final Predicate<File> fileExists = new Predicate<File>() { + private static final Predicate<File> fileExists = new Predicate<File>() { @Override public boolean apply(File input) { return input.exists() && input.canRead(); } }; - private final FilenameFilter layoutFolderFilter = new FilenameFilter() { + private static final FilenameFilter layoutFolderFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith("layout"); } }; - private final FilenameFilter xmlFileFilter = new FilenameFilter() { + private static final FilenameFilter xmlFileFilter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.toLowerCase().endsWith(".xml"); diff --git a/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java b/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java index 63cbc38b..9a7ef2f4 100644 --- a/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java +++ b/compiler/src/main/java/com/android/databinding/reflection/ModelAnalyzer.java @@ -209,8 +209,6 @@ public abstract class ModelAnalyzer { public abstract ModelClass findClass(String className, Map<String, String> imports); - public abstract List<URL> getResources(String name); - public abstract ModelClass findClass(Class classType); public abstract TypeUtil createTypeUtil(); diff --git a/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java b/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java index ca40f2e8..92c6aa8c 100644 --- a/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java +++ b/compiler/src/main/java/com/android/databinding/reflection/annotation/AnnotationAnalyzer.java @@ -439,21 +439,6 @@ public class AnnotationAnalyzer extends ModelAnalyzer { } @Override - public List<URL> getResources(String name) { - ArrayList<URL> urls = new ArrayList<URL>(); - try { - Enumeration<URL> resources = getClass().getClassLoader().getResources(name); - while (resources.hasMoreElements()) { - urls.add(resources.nextElement()); - } - } catch (IOException e) { - L.e(e, "IOException while getting resources:"); - } - - return urls; - } - - @Override public ModelClass findClass(Class classType) { return findClass(classType.getCanonicalName(), null); } diff --git a/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java b/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java index 752562ba..99efe6ba 100644 --- a/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java +++ b/compiler/src/main/java/com/android/databinding/store/LayoutFileParser.java @@ -67,7 +67,7 @@ public class LayoutFileParser { ResourceBundle.LayoutFileBundle bundle = new ResourceBundle.LayoutFileBundle( ParserHelper.INSTANCE$.stripExtension(xml.getName()), layoutId, - xml.getParentFile().getName()); + xml.getParentFile().getName(), pkg); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); final DocumentBuilder builder = factory.newDocumentBuilder(); diff --git a/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java b/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java index 767cc34e..a781417b 100644 --- a/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java +++ b/compiler/src/main/java/com/android/databinding/store/ResourceBundle.java @@ -57,7 +57,15 @@ public class ResourceBundle implements Serializable { mLayoutBundles.put(bundle.mFileName, new ArrayList<LayoutFileBundle>()); } bundle.mLayoutId = layoutId; - mLayoutBundles.get(bundle.mFileName).add(bundle); + final List<LayoutFileBundle> bundles = mLayoutBundles.get(bundle.mFileName); + for (LayoutFileBundle existing : bundles) { + if (existing.equals(bundle)) { + L.d("skipping layout bundle %s because it already exists.", bundle); + return; + } + } + L.d("adding bundle %s", bundle); + bundles.add(bundle); } public HashMap<String, List<LayoutFileBundle>> getLayoutBundles() { @@ -200,6 +208,8 @@ public class ResourceBundle implements Serializable { public int mLayoutId; @XmlAttribute(name="layout", required = true) public String mFileName; + @XmlAttribute(name="modulePackage", required = true) + public String mModulePackage; private String mConfigName; @XmlAttribute(name="directory", required = true) @@ -222,10 +232,12 @@ public class ResourceBundle implements Serializable { public LayoutFileBundle() { } - public LayoutFileBundle(String fileName, int layoutId, String directory) { + public LayoutFileBundle(String fileName, int layoutId, String directory, + String modulePackage) { mFileName = fileName; mLayoutId = layoutId; mDirectory = directory; + mModulePackage = modulePackage; } public void addVariable(String name, String type) { @@ -288,6 +300,57 @@ public class ResourceBundle implements Serializable { public List<BindingTargetBundle> getBindingTargetBundles() { return mBindingTargetBundles; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + LayoutFileBundle bundle = (LayoutFileBundle) o; + + if (mConfigName != null ? !mConfigName.equals(bundle.mConfigName) + : bundle.mConfigName != null) { + return false; + } + if (mDirectory != null ? !mDirectory.equals(bundle.mDirectory) + : bundle.mDirectory != null) { + return false; + } + if (mFileName != null ? !mFileName.equals(bundle.mFileName) + : bundle.mFileName != null) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + int result = mFileName != null ? mFileName.hashCode() : 0; + result = 31 * result + (mConfigName != null ? mConfigName.hashCode() : 0); + result = 31 * result + (mDirectory != null ? mDirectory.hashCode() : 0); + return result; + } + + @Override + public String toString() { + return "LayoutFileBundle{" + + "mHasVariations=" + mHasVariations + + ", mDirectory='" + mDirectory + '\'' + + ", mConfigName='" + mConfigName + '\'' + + ", mModulePackage='" + mModulePackage + '\'' + + ", mFileName='" + mFileName + '\'' + + ", mLayoutId=" + mLayoutId + + '}'; + } + + public String getModulePackage() { + return mModulePackage; + } } @XmlAccessorType(XmlAccessType.NONE) diff --git a/compiler/src/main/java/com/android/databinding/store/SetterStore.java b/compiler/src/main/java/com/android/databinding/store/SetterStore.java index fe14b7e5..a34c8c58 100644 --- a/compiler/src/main/java/com/android/databinding/store/SetterStore.java +++ b/compiler/src/main/java/com/android/databinding/store/SetterStore.java @@ -18,6 +18,7 @@ package com.android.databinding.store; import com.android.databinding.reflection.ModelAnalyzer; import com.android.databinding.reflection.ModelClass; import com.android.databinding.reflection.ModelMethod; +import com.android.databinding.util.GenerationalClassUtil; import com.android.databinding.util.L; import org.apache.commons.lang3.StringUtils; @@ -30,6 +31,7 @@ import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.URL; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -50,7 +52,7 @@ import javax.tools.StandardLocation; public class SetterStore { - public static final String SETTER_STORE_FILE_NAME = "setter_store.bin"; + public static final String SETTER_STORE_FILE_EXT = "-setter_store.bin"; private static SetterStore sStore; @@ -62,70 +64,24 @@ public class SetterStore { mStore = store; } - public static SetterStore get(ProcessingEnvironment processingEnvironment) { - if (sStore == null) { - InputStream in = null; - try { - Filer filer = processingEnvironment.getFiler(); - FileObject resource = filer.getResource(StandardLocation.CLASS_OUTPUT, - SetterStore.class.getPackage().getName(), SETTER_STORE_FILE_NAME); - if (resource != null && new File(resource.getName()).exists()) { - in = resource.openInputStream(); - if (in != null) { - sStore = load(in); - } - } - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } finally { - if (in != null) { - try { - in.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - } - if (sStore == null) { - sStore = new SetterStore(null, new IntermediateV1()); - } - } - return sStore; - } - public static SetterStore get(ModelAnalyzer modelAnalyzer) { if (sStore == null) { - sStore = load(modelAnalyzer); + sStore = load(modelAnalyzer, SetterStore.class.getClassLoader()); } return sStore; } - private static SetterStore load(ModelAnalyzer modelAnalyzer) { + private static SetterStore load(ModelAnalyzer modelAnalyzer, ClassLoader classLoader) { IntermediateV1 store = new IntermediateV1(); - String resourceName = SetterStore.class.getPackage().getName().replace('.', '/') + - '/' + SETTER_STORE_FILE_NAME; - try { - for (URL resource : modelAnalyzer.getResources(resourceName)) { - merge(store, resource); - } - return new SetterStore(modelAnalyzer, store); - } catch (IOException e) { - L.e(e, "Could not read SetterStore intermediate file"); - } catch (ClassNotFoundException e) { - L.e(e, "Could not read SetterStore intermediate file"); + List<Intermediate> previousStores = GenerationalClassUtil + .loadObjects(classLoader, + new GenerationalClassUtil.ExtensionFilter(SETTER_STORE_FILE_EXT)); + for (Intermediate intermediate : previousStores) { + merge(store, intermediate); } return new SetterStore(modelAnalyzer, store); } - private static SetterStore load(InputStream inputStream) - throws IOException, ClassNotFoundException { - ObjectInputStream in = new ObjectInputStream(inputStream); - Intermediate intermediate = (Intermediate) in.readObject(); - return new SetterStore(null, (IntermediateV1) intermediate.upgrade()); - } - public void addRenamedMethod(String attribute, String declaringClass, String method, TypeElement declaredOn) { HashMap<String, MethodDescription> renamed = mStore.renamedMethods.get(attribute); @@ -135,10 +91,12 @@ public class SetterStore { } MethodDescription methodDescription = new MethodDescription(declaredOn.getQualifiedName().toString(), method); + L.d("STORE addmethod desc %s", methodDescription); renamed.put(declaringClass, methodDescription); } public void addBindingAdapter(String attribute, ExecutableElement bindingMethod) { + L.d("STORE addBindingAdapter %s %s", attribute, bindingMethod); HashMap<AccessorKey, MethodDescription> adapters = mStore.adapterMethods.get(attribute); if (adapters == null) { @@ -158,6 +116,7 @@ public class SetterStore { } public void addUntaggableTypes(String[] typeNames, TypeElement declaredOn) { + L.d("STORE addUntaggableTypes %s %s", Arrays.toString(typeNames), declaredOn); String declaredType = declaredOn.getQualifiedName().toString(); for (String type : typeNames) { mStore.untaggableTypes.put(type, declaredType); @@ -187,6 +146,7 @@ public class SetterStore { } public void addConversionMethod(ExecutableElement conversionMethod) { + L.d("STORE addConversionMethod %s", conversionMethod); List<? extends VariableElement> parameters = conversionMethod.getParameters(); String fromType = getQualifiedName(parameters.get(0).asType()); String toType = getQualifiedName(conversionMethod.getReturnType()); @@ -248,21 +208,10 @@ public class SetterStore { keys.clear(); } - public void write(ProcessingEnvironment processingEnvironment) throws IOException { - Filer filer = processingEnvironment.getFiler(); - FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, - SetterStore.class.getPackage().getName(), "setter_store.bin"); - L.d("============= Writing intermediate file: %s", resource.getName()); - ObjectOutputStream out = null; - try { - out = new ObjectOutputStream(resource.openOutputStream()); - - out.writeObject(mStore); - } finally { - if (out != null) { - out.close(); - } - } + public void write(String projectPackage, ProcessingEnvironment processingEnvironment) + throws IOException { + GenerationalClassUtil.writeIntermediateFile(processingEnvironment, + projectPackage, projectPackage + SETTER_STORE_FILE_EXT, mStore); } public SetterCall getSetterCall(String attribute, ModelClass viewType, @@ -503,27 +452,12 @@ public class SetterStore { } } - private static void merge(IntermediateV1 store, - URL nextUrl) throws IOException, ClassNotFoundException { - InputStream inputStream = null; - JarFile jarFile = null; - try { - inputStream = nextUrl.openStream(); - ObjectInputStream in = new ObjectInputStream(inputStream); - Intermediate intermediate = (Intermediate) in.readObject(); - IntermediateV1 intermediateV1 = (IntermediateV1) intermediate.upgrade(); - merge(store.adapterMethods, intermediateV1.adapterMethods); - merge(store.renamedMethods, intermediateV1.renamedMethods); - merge(store.conversionMethods, intermediateV1.conversionMethods); - store.untaggableTypes.putAll(intermediateV1.untaggableTypes); - } finally { - if (inputStream != null) { - inputStream.close(); - } - if (jarFile != null) { - jarFile.close(); - } - } + private static void merge(IntermediateV1 store, Intermediate dumpStore) { + IntermediateV1 intermediateV1 = (IntermediateV1) dumpStore.upgrade(); + merge(store.adapterMethods, intermediateV1.adapterMethods); + merge(store.renamedMethods, intermediateV1.renamedMethods); + merge(store.conversionMethods, intermediateV1.conversionMethods); + store.untaggableTypes.putAll(intermediateV1.untaggableTypes); } private static <K, V> void merge(HashMap<K, HashMap<V, MethodDescription>> first, @@ -619,7 +553,7 @@ public class SetterStore { } } - private interface Intermediate { + private interface Intermediate extends Serializable { Intermediate upgrade(); } diff --git a/compiler/src/main/java/com/android/databinding/util/GenerationalClassUtil.java b/compiler/src/main/java/com/android/databinding/util/GenerationalClassUtil.java new file mode 100644 index 00000000..c970ad53 --- /dev/null +++ b/compiler/src/main/java/com/android/databinding/util/GenerationalClassUtil.java @@ -0,0 +1,187 @@ +/* + * 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 com.android.databinding.util; + +import com.android.databinding.util.L; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.filefilter.IOFileFilter; +import org.apache.commons.io.filefilter.TrueFileFilter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.tools.FileObject; +import javax.tools.StandardLocation; + +/** + * A utility class that helps adding build specific objects to the jar file + * and their extraction later on. + */ +public class GenerationalClassUtil { + public static <T extends Serializable> List<T> loadObjects(ClassLoader classLoader, Filter filter) { + final List<T> result = new ArrayList<T>(); + if (!(classLoader instanceof URLClassLoader)) { + L.d("class loader is not url class loader (%s). skipping.", classLoader.getClass()); + return result; + } + final URLClassLoader urlClassLoader = (URLClassLoader) classLoader; + for (URL url : urlClassLoader.getURLs()) { + L.d("checking url %s for intermediate data", url); + try { + final File file = new File(url.toURI()); + if (!file.exists()) { + L.d("cannot load file for %s", url); + continue; + } + if (file.isDirectory()) { + // probably exported classes dir. + loadFromDirectory(filter, result, file); + } else { + // assume it is a zip file + loadFomZipFile(filter, result, file); + } + } catch (IOException e) { + L.d("cannot open zip file from %s", url); + } catch (URISyntaxException e) { + L.d("cannot open zip file from %s", url); + } + } + return result; + } + + private static <T extends Serializable> void loadFromDirectory(final Filter filter, List<T> result, + File directory) { + //noinspection unchecked + Collection<File> files = FileUtils.listFiles(directory, new IOFileFilter() { + @Override + public boolean accept(File file) { + return filter.accept(file.getName()); + } + + @Override + public boolean accept(File dir, String name) { + return filter.accept(name); + } + }, TrueFileFilter.INSTANCE); + for (File file : files) { + InputStream inputStream = null; + try { + inputStream = FileUtils.openInputStream(file); + T item = fromInputStream(result, inputStream); + L.d("loaded item %s from file", item); + if (item != null) { + result.add(item); + } + } catch (IOException e) { + L.e(e, "Could not merge in Bindables from %s", file.getAbsolutePath()); + } catch (ClassNotFoundException e) { + L.e(e, "Could not read Binding properties intermediate file. %s", file.getAbsolutePath()); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + } + + private static <T extends Serializable> void loadFomZipFile(Filter filter, + List<T> result, File file) throws IOException { + ZipFile zipFile = new ZipFile(file); + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + if (!filter.accept(entry.getName())) { + continue; + } + L.d("loading data from file %s", entry.getName()); + InputStream inputStream = null; + try { + inputStream = zipFile.getInputStream(entry); + T item = fromInputStream(result, inputStream); + L.d("loaded item %s from zip file", item); + if (item != null) { + result.add(item); + } + } catch (IOException e) { + L.e(e, "Could not merge in Bindables from %s", file.getAbsolutePath()); + } catch (ClassNotFoundException e) { + L.e(e, "Could not read Binding properties intermediate file. %s", file.getAbsolutePath()); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + } + + private static <T extends Serializable> T fromInputStream(List<T> result, + InputStream inputStream) throws IOException, ClassNotFoundException { + ObjectInputStream in = new ObjectInputStream(inputStream); + return (T) in.readObject(); + + } + + public static void writeIntermediateFile(ProcessingEnvironment processingEnv, + String packageName, String fileName, Serializable object) { + ObjectOutputStream oos = null; + try { + FileObject intermediate = processingEnv.getFiler().createResource( + StandardLocation.CLASS_OUTPUT, packageName, + fileName); + OutputStream ios = intermediate.openOutputStream(); + oos = new ObjectOutputStream(ios); + oos.writeObject(object); + oos.close(); + L.d("wrote intermediate bindable file %s %s", packageName, fileName); + } catch (IOException e) { + L.e(e, "Could not write to intermediate file: %s", fileName); + } finally { + IOUtils.closeQuietly(oos); + } + } + + + public static interface Filter { + public boolean accept(String entryName); + } + + public static class ExtensionFilter implements Filter { + private final String mExtension; + public ExtensionFilter(String extension) { + mExtension = extension; + } + + @Override + public boolean accept(String entryName) { + return entryName.endsWith(mExtension); + } + } +} diff --git a/compiler/src/main/java/com/android/databinding/writer/AnnotationJavaFileWriter.java b/compiler/src/main/java/com/android/databinding/writer/AnnotationJavaFileWriter.java index 67d274ed..d24ac04a 100644 --- a/compiler/src/main/java/com/android/databinding/writer/AnnotationJavaFileWriter.java +++ b/compiler/src/main/java/com/android/databinding/writer/AnnotationJavaFileWriter.java @@ -15,8 +15,14 @@ */ package com.android.databinding.writer; +import com.google.common.base.Preconditions; + +import com.android.databinding.util.L; + +import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; +import java.io.File; import java.io.IOException; import java.io.Writer; @@ -24,7 +30,7 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.tools.Diagnostic; import javax.tools.JavaFileObject; -public class AnnotationJavaFileWriter implements JavaFileWriter { +public class AnnotationJavaFileWriter extends JavaFileWriter { private final ProcessingEnvironment mProcessingEnvironment; public AnnotationJavaFileWriter(ProcessingEnvironment processingEnvironment) { @@ -35,13 +41,13 @@ public class AnnotationJavaFileWriter implements JavaFileWriter { public void writeToFile(String canonicalName, String contents) { Writer writer = null; try { + L.d("writing file %s", canonicalName); JavaFileObject javaFileObject = mProcessingEnvironment.getFiler().createSourceFile(canonicalName); writer = javaFileObject.openWriter(); writer.write(contents); } catch (IOException e) { - mProcessingEnvironment.getMessager().printMessage(Diagnostic.Kind.ERROR, - "Could not write to " + canonicalName + ": " + e.getLocalizedMessage()); + L.e(e, "Could not write to %s", canonicalName); } finally { if (writer != null) { IOUtils.closeQuietly(writer); diff --git a/compiler/src/main/java/com/android/databinding/writer/JavaFileWriter.java b/compiler/src/main/java/com/android/databinding/writer/JavaFileWriter.java index f7b3c9ce..9fa93477 100644 --- a/compiler/src/main/java/com/android/databinding/writer/JavaFileWriter.java +++ b/compiler/src/main/java/com/android/databinding/writer/JavaFileWriter.java @@ -13,6 +13,23 @@ package com.android.databinding.writer; -public interface JavaFileWriter { - public void writeToFile(String canonicalName, String contents); +import com.android.databinding.util.L; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.io.IOException; + +public abstract class JavaFileWriter { + public abstract void writeToFile(String canonicalName, String contents); + public void writeToFile(File exactPath, String contents) { + File parent = exactPath.getParentFile(); + parent.mkdirs(); + try { + L.d("writing file %s", exactPath.getAbsoluteFile()); + FileUtils.writeStringToFile(exactPath, contents); + } catch (IOException e) { + L.e(e, "Could not write to %s", exactPath); + } + } } |