/* * 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.annotationprocessor; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import android.databinding.BindingBuildInfo; import android.databinding.tool.CompilerChef; import android.databinding.tool.reflection.SdkUtil; import android.databinding.tool.store.ResourceBundle; import android.databinding.tool.util.GenerationalClassUtil; import android.databinding.tool.util.L; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.RoundEnvironment; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; public class ProcessExpressions extends ProcessDataBinding.ProcessingStep { public ProcessExpressions() { } @Override public boolean onHandleStep(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) { ResourceBundle resourceBundle; SdkUtil.initialize(buildInfo.minSdk(), new File(buildInfo.sdkRoot())); resourceBundle = new ResourceBundle(buildInfo.modulePackage()); List<Intermediate> intermediateList = GenerationalClassUtil.loadObjects( GenerationalClassUtil.ExtensionFilter.LAYOUT); IntermediateV1 mine = createIntermediateFromLayouts(buildInfo.layoutInfoDir()); if (mine != null) { mine.removeOverridden(intermediateList); intermediateList.add(mine); saveIntermediate(processingEnvironment, buildInfo, mine); } // generate them here so that bindable parser can read try { generateBinders(resourceBundle, buildInfo, intermediateList); } catch (Throwable t) { L.e(t, "cannot generate view binders"); } return true; } private void saveIntermediate(ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo, IntermediateV1 intermediate) { GenerationalClassUtil.writeIntermediateFile(processingEnvironment, buildInfo.modulePackage(), buildInfo.modulePackage() + GenerationalClassUtil.ExtensionFilter.LAYOUT.getExtension(), intermediate); } @Override public void onProcessingOver(RoundEnvironment roundEnvironment, ProcessingEnvironment processingEnvironment, BindingBuildInfo buildInfo) { } private void generateBinders(ResourceBundle resourceBundle, BindingBuildInfo buildInfo, List<Intermediate> intermediates) throws Throwable { for (Intermediate intermediate : intermediates) { intermediate.appendTo(resourceBundle); } writeResourceBundle(resourceBundle, buildInfo.isLibrary(), buildInfo.minSdk(), buildInfo.exportClassListTo()); } private IntermediateV1 createIntermediateFromLayouts(String layoutInfoFolderPath) { final File layoutInfoFolder = new File(layoutInfoFolderPath); if (!layoutInfoFolder.isDirectory()) { L.d("layout info folder does not exist, skipping for %s", layoutInfoFolderPath); return null; } IntermediateV1 result = new IntermediateV1(); for (File layoutFile : layoutInfoFolder.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".xml"); } })) { try { result.addEntry(layoutFile.getName(), FileUtils.readFileToString(layoutFile)); } catch (IOException e) { L.e(e, "cannot load layout file information. Try a clean build"); } } return result; } private void writeResourceBundle(ResourceBundle resourceBundle, boolean forLibraryModule, final int minSdk, String exportClassNamesTo) throws JAXBException { final CompilerChef compilerChef = CompilerChef.createChef(resourceBundle, getWriter()); compilerChef.sealModels(); compilerChef.writeComponent(); if (compilerChef.hasAnythingToGenerate()) { compilerChef.writeViewBinderInterfaces(forLibraryModule); if (!forLibraryModule) { compilerChef.writeViewBinders(minSdk); } } if (forLibraryModule && exportClassNamesTo == null) { L.e("When compiling a library module, build info must include exportClassListTo path"); } if (forLibraryModule) { Set<String> classNames = compilerChef.getWrittenClassNames(); String out = StringUtils.join(classNames, System.getProperty("line.separator")); L.d("Writing list of classes to %s . \nList:%s", exportClassNamesTo, out); try { //noinspection ConstantConditions FileUtils.write(new File(exportClassNamesTo), out); } catch (IOException e) { L.e(e, "Cannot create list of written classes"); } } mCallback.onChefReady(compilerChef, forLibraryModule, minSdk); } public static interface Intermediate extends Serializable { Intermediate upgrade(); public void appendTo(ResourceBundle resourceBundle) throws Throwable; } public static class IntermediateV1 implements Intermediate { transient Unmarshaller mUnmarshaller; // name to xml content map Map<String, String> mLayoutInfoMap = new HashMap<String, String>(); @Override public Intermediate upgrade() { return this; } @Override public void appendTo(ResourceBundle resourceBundle) throws JAXBException { if (mUnmarshaller == null) { JAXBContext context = JAXBContext .newInstance(ResourceBundle.LayoutFileBundle.class); mUnmarshaller = context.createUnmarshaller(); } for (String content : mLayoutInfoMap.values()) { final InputStream is = IOUtils.toInputStream(content); try { final ResourceBundle.LayoutFileBundle bundle = (ResourceBundle.LayoutFileBundle) mUnmarshaller.unmarshal(is); resourceBundle.addLayoutBundle(bundle); L.d("loaded layout info file %s", bundle); } finally { IOUtils.closeQuietly(is); } } } public void addEntry(String name, String contents) { mLayoutInfoMap.put(name, contents); } public void removeOverridden(List<Intermediate> existing) { // this is the way we get rid of files that are copied from previous modules // it is important to do this before saving the intermediate file for (Intermediate old : existing) { if (old instanceof IntermediateV1) { IntermediateV1 other = (IntermediateV1) old; for (String key : other.mLayoutInfoMap.keySet()) { // TODO we should consider the original file as the key here // but aapt probably cannot provide that information if (mLayoutInfoMap.remove(key) != null) { L.d("removing %s from bundle because it came from another module", key); } } } } } } }