android.databinding.tool.MakeCopy.java Source code

Java tutorial

Introduction

Here is the source code for android.databinding.tool.MakeCopy.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 org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.w3c.dom.Document;

import android.databinding.tool.store.ResourceBundle.LayoutFileBundle;
import android.databinding.tool.util.GenerationalClassUtil;
import android.databinding.tool.writer.JavaFileWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

/**
 * This class is used by make to copy resources to an intermediate directory and start processing
 * them. When aapt takes over, this can be easily extracted to a short script.
 */
public class MakeCopy {
    private static final int MANIFEST_INDEX = 0;
    private static final int SRC_INDEX = 1;
    private static final int XML_INDEX = 2;
    private static final int RES_OUT_INDEX = 3;
    private static final int RES_IN_INDEX = 4;

    private static final String APP_SUBPATH = LayoutXmlProcessor.RESOURCE_BUNDLE_PACKAGE.replace('.',
            File.separatorChar);
    private static final FilenameFilter LAYOUT_DIR_FILTER = new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return name.toLowerCase().startsWith("layout");
        }
    };

    private static final FilenameFilter XML_FILENAME_FILTER = new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return name.toLowerCase().endsWith(".xml");
        }
    };

    public static void main(String[] args) {
        if (args.length < 5) {
            System.out.println("required parameters: [-l] manifest adk-dir src-out-dir xml-out-dir "
                    + "res-out-dir res-in-dir...");
            System.out.println("Creates an android data binding class and copies resources from");
            System.out.println("res-source to res-target and modifies binding layout files");
            System.out.println("in res-target. Binding data is extracted into XML files");
            System.out.println("and placed in xml-out-dir.");
            System.out.println("  -l          indicates that this is a library");
            System.out.println("  manifest    path to AndroidManifest.xml file");
            System.out.println("  src-out-dir path to where generated source goes");
            System.out.println("  xml-out-dir path to where generated binding XML goes");
            System.out.println("  res-out-dir path to the where modified resources should go");
            System.out.println(
                    "  res-in-dir  path to source resources \"res\" directory. One" + " or more are allowed.");
            System.exit(1);
        }
        final boolean isLibrary = args[0].equals("-l");
        final int indexOffset = isLibrary ? 1 : 0;
        final String applicationPackage;
        final int minSdk;
        final Document androidManifest = readAndroidManifest(new File(args[MANIFEST_INDEX + indexOffset]));
        try {
            final XPathFactory xPathFactory = XPathFactory.newInstance();
            final XPath xPath = xPathFactory.newXPath();
            applicationPackage = xPath.evaluate("string(/manifest/@package)", androidManifest);
            final Double minSdkNumber = (Double) xPath.evaluate("number(/manifest/uses-sdk/@android:minSdkVersion)",
                    androidManifest, XPathConstants.NUMBER);
            minSdk = minSdkNumber == null ? 1 : minSdkNumber.intValue();
        } catch (XPathExpressionException e) {
            e.printStackTrace();
            System.exit(6);
            return;
        }
        final File srcDir = new File(args[SRC_INDEX + indexOffset], APP_SUBPATH);
        if (!makeTargetDir(srcDir)) {
            System.err.println("Could not create source directory " + srcDir);
            System.exit(2);
        }
        final File resTarget = new File(args[RES_OUT_INDEX + indexOffset]);
        if (!makeTargetDir(resTarget)) {
            System.err.println("Could not create resource directory: " + resTarget);
            System.exit(4);
        }
        final File xmlDir = new File(args[XML_INDEX + indexOffset]);
        if (!makeTargetDir(xmlDir)) {
            System.err.println("Could not create xml output directory: " + xmlDir);
            System.exit(5);
        }
        System.out.println("Application Package: " + applicationPackage);
        System.out.println("Minimum SDK: " + minSdk);
        System.out.println("Target Resources: " + resTarget.getAbsolutePath());
        System.out.println("Target Source Dir: " + srcDir.getAbsolutePath());
        System.out.println("Target XML Dir: " + xmlDir.getAbsolutePath());
        System.out.println("Library? " + isLibrary);

        boolean foundSomeResources = false;
        for (int i = RES_IN_INDEX + indexOffset; i < args.length; i++) {
            final File resDir = new File(args[i]);
            if (!resDir.exists()) {
                System.out.println("Could not find resource directory: " + resDir);
            } else {
                System.out.println("Source Resources: " + resDir.getAbsolutePath());
                try {
                    FileUtils.copyDirectory(resDir, resTarget);
                    addFromFile(resDir, resTarget);
                    foundSomeResources = true;
                } catch (IOException e) {
                    System.err.println("Could not copy resources from " + resDir + " to " + resTarget + ": "
                            + e.getLocalizedMessage());
                    System.exit(3);
                }
            }
        }

        if (!foundSomeResources) {
            System.err.println("No resource directories were found.");
            System.exit(7);
        }
        processLayoutFiles(applicationPackage, resTarget, srcDir, xmlDir, minSdk, isLibrary);
    }

    private static Document readAndroidManifest(File manifest) {
        try {
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
            return documentBuilder.parse(manifest);
        } catch (Exception e) {
            System.err.println("Could not load Android Manifest from " + manifest.getAbsolutePath() + ": "
                    + e.getLocalizedMessage());
            System.exit(8);
            return null;
        }
    }

    private static void processLayoutFiles(String applicationPackage, File resTarget, File srcDir, File xmlDir,
            int minSdk, boolean isLibrary) {
        ArrayList<File> resourceFolders = new ArrayList<File>();
        resourceFolders.add(resTarget);
        MakeFileWriter makeFileWriter = new MakeFileWriter(srcDir);
        LayoutXmlProcessor xmlProcessor = new LayoutXmlProcessor(applicationPackage, resourceFolders,
                makeFileWriter, minSdk, isLibrary);
        try {
            xmlProcessor.processResources(minSdk);
            xmlProcessor.writeLayoutInfoFiles(xmlDir);
            // TODO Looks like make does not support excluding from libs ?
            xmlProcessor.writeInfoClass(null, xmlDir, null);
            Map<String, List<LayoutFileBundle>> bundles = xmlProcessor.getResourceBundle().getLayoutBundles();
            if (isLibrary) {
                for (String name : bundles.keySet()) {
                    LayoutFileBundle layoutFileBundle = bundles.get(name).get(0);
                    String pkgName = layoutFileBundle.getBindingClassPackage().replace('.', '/');
                    System.err.println(pkgName + '/' + layoutFileBundle.getBindingClassName() + ".class");
                }
            }
            if (makeFileWriter.getErrorCount() > 0) {
                System.exit(9);
            }
        } catch (Exception e) {
            System.err.println("Error processing layout files: " + e.getLocalizedMessage());
            System.exit(10);
        }
    }

    private static void addFromFile(File resDir, File resTarget) {
        for (File layoutDir : resDir.listFiles(LAYOUT_DIR_FILTER)) {
            if (layoutDir.isDirectory()) {
                File targetDir = new File(resTarget, layoutDir.getName());
                for (File layoutFile : layoutDir.listFiles(XML_FILENAME_FILTER)) {
                    File targetFile = new File(targetDir, layoutFile.getName());
                    FileWriter appender = null;
                    try {
                        appender = new FileWriter(targetFile, true);
                        appender.write("<!-- From: " + layoutFile.toURI().toString() + " -->\n");
                    } catch (IOException e) {
                        System.err.println("Could not update " + layoutFile + ": " + e.getLocalizedMessage());
                    } finally {
                        IOUtils.closeQuietly(appender);
                    }
                }
            }
        }
    }

    private static boolean makeTargetDir(File dir) {
        if (dir.exists()) {
            return dir.isDirectory();
        }

        return dir.mkdirs();
    }

    private static class MakeFileWriter extends JavaFileWriter {
        private final File mSourceRoot;
        private int mErrorCount;

        public MakeFileWriter(File sourceRoot) {
            mSourceRoot = sourceRoot;
        }

        @Override
        public void writeToFile(String canonicalName, String contents) {
            String fileName = canonicalName.replace('.', File.separatorChar) + ".java";
            File sourceFile = new File(mSourceRoot, fileName);
            FileWriter writer = null;
            try {
                sourceFile.getParentFile().mkdirs();
                writer = new FileWriter(sourceFile);
                writer.write(contents);
            } catch (IOException e) {
                System.err.println("Could not write to " + sourceFile + ": " + e.getLocalizedMessage());
                mErrorCount++;
            } finally {
                IOUtils.closeQuietly(writer);
            }
        }

        public int getErrorCount() {
            return mErrorCount;
        }
    }
}