android.databinding.tool.DataBinderPlugin.java Source code

Java tutorial

Introduction

Here is the source code for android.databinding.tool.DataBinderPlugin.java

Source

/*
 * 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 com.google.common.base.Preconditions;

import com.android.build.gradle.AppExtension;
import com.android.build.gradle.BaseExtension;
import com.android.build.gradle.LibraryExtension;
import com.android.build.gradle.api.ApplicationVariant;
import com.android.build.gradle.api.LibraryVariant;
import com.android.build.gradle.api.TestVariant;
import com.android.build.gradle.internal.api.ApplicationVariantImpl;
import com.android.build.gradle.internal.api.LibraryVariantImpl;
import com.android.build.gradle.internal.api.TestVariantImpl;
import com.android.build.gradle.internal.core.GradleVariantConfiguration;
import com.android.build.gradle.internal.variant.ApplicationVariantData;
import com.android.build.gradle.internal.variant.BaseVariantData;
import com.android.build.gradle.internal.variant.LibraryVariantData;
import com.android.build.gradle.internal.variant.TestVariantData;
import com.android.build.gradle.tasks.ProcessAndroidResources;
import com.android.builder.model.ApiVersion;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.gradle.api.Action;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.Task;
import org.gradle.api.logging.LogLevel;
import org.gradle.api.logging.Logger;
import org.gradle.api.plugins.ExtraPropertiesExtension;
import org.gradle.api.tasks.bundling.Jar;
import org.gradle.api.tasks.compile.AbstractCompile;

import android.databinding.tool.processing.ScopedException;
import android.databinding.tool.util.L;
import android.databinding.tool.writer.JavaFileWriter;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;

import javax.tools.Diagnostic;
import javax.xml.bind.JAXBException;

public class DataBinderPlugin implements Plugin<Project> {

    private static final String INVOKED_FROM_IDE_PROPERTY = "android.injected.invoked.from.ide";
    private static final String PRINT_ENCODED_ERRORS_PROPERTY = "android.databinding.injected.print.encoded.errors";
    private Logger logger;
    private boolean printEncodedErrors = false;

    class GradleFileWriter extends JavaFileWriter {

        private final String outputBase;

        public GradleFileWriter(String outputBase) {
            this.outputBase = outputBase;
        }

        @Override
        public void writeToFile(String canonicalName, String contents) {
            String asPath = canonicalName.replace('.', '/');
            File f = new File(outputBase + "/" + asPath + ".java");
            logD("Asked to write to " + canonicalName + ". outputting to:" + f.getAbsolutePath());
            //noinspection ResultOfMethodCallIgnored
            f.getParentFile().mkdirs();
            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(f);
                IOUtils.write(contents, fos);
            } catch (IOException e) {
                logE(e, "cannot write file " + f.getAbsolutePath());
            } finally {
                IOUtils.closeQuietly(fos);
            }
        }
    }

    private boolean safeGetBooleanProperty(Project project, String property) {
        boolean hasProperty = project.hasProperty(property);
        if (!hasProperty) {
            return false;
        }
        try {
            if (Boolean.parseBoolean(String.valueOf(project.getProperties().get(property)))) {
                return true;
            }
        } catch (Throwable t) {
            L.w("unable to read property %s", project);
        }
        return false;
    }

    private boolean resolvePrintEncodedErrors(Project project) {
        return safeGetBooleanProperty(project, INVOKED_FROM_IDE_PROPERTY)
                || safeGetBooleanProperty(project, PRINT_ENCODED_ERRORS_PROPERTY);
    }

    @Override
    public void apply(Project project) {
        if (project == null) {
            return;
        }
        setupLogger(project);

        String myVersion = readMyVersion();
        logD("data binding plugin version is %s", myVersion);
        if (StringUtils.isEmpty(myVersion)) {
            throw new IllegalStateException("cannot read version of the plugin :/");
        }
        printEncodedErrors = resolvePrintEncodedErrors(project);
        ScopedException.encodeOutput(printEncodedErrors);
        project.getDependencies().add("compile", "com.android.databinding:library:" + myVersion);
        boolean addAdapters = true;
        if (project.hasProperty("ext")) {
            Object ext = project.getProperties().get("ext");
            if (ext instanceof ExtraPropertiesExtension) {
                ExtraPropertiesExtension propExt = (ExtraPropertiesExtension) ext;
                if (propExt.has("addDataBindingAdapters")) {
                    addAdapters = Boolean.valueOf(String.valueOf(propExt.get("addDataBindingAdapters")));
                }
            }
        }
        if (addAdapters) {
            project.getDependencies().add("compile", "com.android.databinding:adapters:" + myVersion);
        }
        project.getDependencies().add("provided", "com.android.databinding:compiler:" + myVersion);
        project.afterEvaluate(new Action<Project>() {
            @Override
            public void execute(Project project) {
                try {
                    createXmlProcessor(project);
                } catch (Throwable t) {
                    logE(t, "failed to setup data binding");
                }
            }
        });
    }

    private void setupLogger(Project project) {
        logger = project.getLogger();
        L.setClient(new L.Client() {
            @Override
            public void printMessage(Diagnostic.Kind kind, String message) {
                if (kind == Diagnostic.Kind.ERROR) {
                    logE(null, message);
                } else {
                    logD(message);
                }
            }
        });
    }

    String readMyVersion() {
        try {
            InputStream stream = getClass().getResourceAsStream("/data_binding_build_info");
            try {
                return IOUtils.toString(stream, "utf-8").trim();
            } finally {
                IOUtils.closeQuietly(stream);
            }
        } catch (IOException exception) {
            logE(exception, "Cannot read data binding version");
        }
        return null;
    }

    private void createXmlProcessor(Project project) throws NoSuchFieldException, IllegalAccessException {
        L.d("creating xml processor for " + project);
        Object androidExt = project.getExtensions().getByName("android");
        if (!(androidExt instanceof BaseExtension)) {
            return;
        }
        if (androidExt instanceof AppExtension) {
            createXmlProcessorForApp(project, (AppExtension) androidExt);
        } else if (androidExt instanceof LibraryExtension) {
            createXmlProcessorForLibrary(project, (LibraryExtension) androidExt);
        } else {
            logE(new UnsupportedOperationException("cannot understand android ext"),
                    "unsupported android extension. What is it? %s", androidExt);
        }
    }

    private void createXmlProcessorForLibrary(Project project, LibraryExtension lib)
            throws NoSuchFieldException, IllegalAccessException {
        File sdkDir = lib.getSdkDirectory();
        L.d("create xml processor for " + lib);
        for (TestVariant variant : lib.getTestVariants()) {
            logD("test variant %s. dir name %s", variant, variant.getDirName());
            BaseVariantData variantData = getVariantData(variant);
            attachXmlProcessor(project, variantData, sdkDir, false);//tests extend apk variant
        }
        for (LibraryVariant variant : lib.getLibraryVariants()) {
            logD("library variant %s. dir name %s", variant, variant.getDirName());
            BaseVariantData variantData = getVariantData(variant);
            attachXmlProcessor(project, variantData, sdkDir, true);
        }
    }

    private void createXmlProcessorForApp(Project project, AppExtension appExt)
            throws NoSuchFieldException, IllegalAccessException {
        L.d("create xml processor for " + appExt);
        File sdkDir = appExt.getSdkDirectory();
        for (TestVariant testVariant : appExt.getTestVariants()) {
            TestVariantData variantData = getVariantData(testVariant);
            attachXmlProcessor(project, variantData, sdkDir, false);
        }
        for (ApplicationVariant appVariant : appExt.getApplicationVariants()) {
            ApplicationVariantData variantData = getVariantData(appVariant);
            attachXmlProcessor(project, variantData, sdkDir, false);
        }
    }

    private LibraryVariantData getVariantData(LibraryVariant variant)
            throws NoSuchFieldException, IllegalAccessException {
        Field field = LibraryVariantImpl.class.getDeclaredField("variantData");
        field.setAccessible(true);
        return (LibraryVariantData) field.get(variant);
    }

    private TestVariantData getVariantData(TestVariant variant)
            throws IllegalAccessException, NoSuchFieldException {
        Field field = TestVariantImpl.class.getDeclaredField("variantData");
        field.setAccessible(true);
        return (TestVariantData) field.get(variant);
    }

    private ApplicationVariantData getVariantData(ApplicationVariant variant)
            throws IllegalAccessException, NoSuchFieldException {
        Field field = ApplicationVariantImpl.class.getDeclaredField("variantData");
        field.setAccessible(true);
        return (ApplicationVariantData) field.get(variant);
    }

    private void attachXmlProcessor(Project project, final BaseVariantData variantData, final File sdkDir,
            final Boolean isLibrary) {
        final GradleVariantConfiguration configuration = variantData.getVariantConfiguration();
        final ApiVersion minSdkVersion = configuration.getMinSdkVersion();
        ProcessAndroidResources generateRTask = variantData.generateRClassTask;
        final String packageName = generateRTask.getPackageForR();
        String fullName = configuration.getFullName();
        List<File> resourceFolders = Arrays.asList(variantData.mergeResourcesTask.getOutputDir());

        final File codeGenTargetFolder = new File(
                project.getBuildDir() + "/data-binding-info/" + configuration.getDirName());
        String writerOutBase = codeGenTargetFolder.getAbsolutePath();
        JavaFileWriter fileWriter = new GradleFileWriter(writerOutBase);
        final LayoutXmlProcessor xmlProcessor = new LayoutXmlProcessor(packageName, resourceFolders, fileWriter,
                minSdkVersion.getApiLevel(), isLibrary);
        final ProcessAndroidResources processResTask = generateRTask;
        final File xmlOutDir = new File(project.getBuildDir() + "/layout-info/" + configuration.getDirName());
        final File generatedClassListOut = isLibrary ? new File(xmlOutDir, "_generated.txt") : null;
        logD("xml output for %s is %s", variantData, xmlOutDir);
        String layoutTaskName = "dataBindingLayouts" + StringUtils.capitalize(processResTask.getName());
        String infoClassTaskName = "dataBindingInfoClass" + StringUtils.capitalize(processResTask.getName());

        final DataBindingProcessLayoutsTask[] processLayoutsTasks = new DataBindingProcessLayoutsTask[1];
        project.getTasks().create(layoutTaskName, DataBindingProcessLayoutsTask.class,
                new Action<DataBindingProcessLayoutsTask>() {
                    @Override
                    public void execute(final DataBindingProcessLayoutsTask task) {
                        processLayoutsTasks[0] = task;
                        task.setXmlProcessor(xmlProcessor);
                        task.setSdkDir(sdkDir);
                        task.setXmlOutFolder(xmlOutDir);
                        task.setMinSdk(minSdkVersion.getApiLevel());

                        logD("TASK adding dependency on %s for %s", task, processResTask);
                        processResTask.dependsOn(task);
                        processResTask.getInputs().dir(xmlOutDir);
                        for (Object dep : processResTask.getDependsOn()) {
                            if (dep == task) {
                                continue;
                            }
                            logD("adding dependency on %s for %s", dep, task);
                            task.dependsOn(dep);
                        }
                        processResTask.doLast(new Action<Task>() {
                            @Override
                            public void execute(Task unused) {
                                try {
                                    task.writeLayoutXmls();
                                } catch (JAXBException e) {
                                    // gradle sometimes fails to resolve JAXBException.
                                    // We get stack trace manually to ensure we have the log
                                    logE(e, "cannot write layout xmls %s", ExceptionUtils.getStackTrace(e));
                                }
                            }
                        });
                    }
                });
        final DataBindingProcessLayoutsTask processLayoutsTask = processLayoutsTasks[0];
        project.getTasks().create(infoClassTaskName, DataBindingExportInfoTask.class,
                new Action<DataBindingExportInfoTask>() {

                    @Override
                    public void execute(DataBindingExportInfoTask task) {
                        task.dependsOn(processLayoutsTask);
                        task.dependsOn(processResTask);
                        task.setXmlProcessor(xmlProcessor);
                        task.setSdkDir(sdkDir);
                        task.setXmlOutFolder(xmlOutDir);
                        task.setExportClassListTo(generatedClassListOut);
                        task.setPrintEncodedErrors(printEncodedErrors);
                        task.setEnableDebugLogs(logger.isEnabled(LogLevel.DEBUG));

                        variantData.registerJavaGeneratingTask(task, codeGenTargetFolder);
                    }
                });
        String packageJarTaskName = "package" + StringUtils.capitalize(fullName) + "Jar";
        final Task packageTask = project.getTasks().findByName(packageJarTaskName);
        if (packageTask instanceof Jar) {
            String removeGeneratedTaskName = "dataBindingExcludeGeneratedFrom"
                    + StringUtils.capitalize(packageTask.getName());
            if (project.getTasks().findByName(removeGeneratedTaskName) == null) {
                final AbstractCompile javaCompileTask = variantData.javacTask;
                Preconditions.checkNotNull(javaCompileTask);

                project.getTasks().create(removeGeneratedTaskName, DataBindingExcludeGeneratedTask.class,
                        new Action<DataBindingExcludeGeneratedTask>() {
                            @Override
                            public void execute(DataBindingExcludeGeneratedTask task) {
                                packageTask.dependsOn(task);
                                task.dependsOn(javaCompileTask);
                                task.setAppPackage(packageName);
                                task.setInfoClassQualifiedName(xmlProcessor.getInfoClassFullName());
                                task.setPackageTask((Jar) packageTask);
                                task.setLibrary(isLibrary);
                                task.setGeneratedClassListFile(generatedClassListOut);
                            }
                        });
            }
        }
    }

    private void logD(String s, Object... args) {
        logger.info(formatLog(s, args));
    }

    private void logE(Throwable t, String s, Object... args) {
        logger.error(formatLog(s, args), t);
    }

    private String formatLog(String s, Object... args) {
        return "[data binding plugin]: " + String.format(s, args);
    }
}