MakeCopy.java revision ee7586713d68806b556a425cbebf007a56261ff3
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.databinding.tool;
17
18import org.apache.commons.io.FileUtils;
19import org.apache.commons.io.IOUtils;
20import org.w3c.dom.Document;
21
22import android.databinding.tool.writer.JavaFileWriter;
23
24import java.io.File;
25import java.io.FileWriter;
26import java.io.FilenameFilter;
27import java.io.IOException;
28import java.util.ArrayList;
29
30import javax.xml.parsers.DocumentBuilder;
31import javax.xml.parsers.DocumentBuilderFactory;
32import javax.xml.xpath.XPath;
33import javax.xml.xpath.XPathConstants;
34import javax.xml.xpath.XPathExpressionException;
35import javax.xml.xpath.XPathFactory;
36
37/**
38 * This class is used by make to copy resources to an intermediate directory and start processing
39 * them. When aapt takes over, this can be easily extracted to a short script.
40 */
41public class MakeCopy {
42    private static final int MANIFEST_INDEX = 0;
43    private static final int SRC_INDEX = 1;
44    private static final int XML_INDEX = 2;
45    private static final int RES_OUT_INDEX = 3;
46    private static final int RES_IN_INDEX = 4;
47
48    private static final String APP_SUBPATH = LayoutXmlProcessor.RESOURCE_BUNDLE_PACKAGE
49            .replace('.', File.separatorChar);
50    private static final FilenameFilter LAYOUT_DIR_FILTER = new FilenameFilter() {
51        @Override
52        public boolean accept(File dir, String name) {
53            return name.toLowerCase().startsWith("layout");
54        }
55    };
56
57    private static final FilenameFilter XML_FILENAME_FILTER = new FilenameFilter() {
58        @Override
59        public boolean accept(File dir, String name) {
60            return name.toLowerCase().endsWith(".xml");
61        }
62    };
63
64    public static void main(String[] args) {
65        if (args.length < 5) {
66            System.out.println("required parameters: manifest adk-dir src-out-dir xml-out-dir " +
67                            "res-out-dir res-in-dir...");
68            System.out.println("Creates an android data binding class and copies resources from");
69            System.out.println("res-source to res-target and modifies binding layout files");
70            System.out.println("in res-target. Binding data is extracted into XML files");
71            System.out.println("and placed in xml-out-dir.");
72            System.out.println("  manifest    path to AndroidManifest.xml file");
73            System.out.println("  src-out-dir path to where generated source goes");
74            System.out.println("  xml-out-dir path to where generated binding XML goes");
75            System.out.println("  res-out-dir path to the where modified resources should go");
76            System.out.println("  res-in-dir  path to source resources \"res\" directory. One" +
77                    " or more are allowed.");
78            System.exit(1);
79        }
80        final boolean isLibrary;
81        final String applicationPackage;
82        final int minSdk;
83        final Document androidManifest = readAndroidManifest(new File(args[MANIFEST_INDEX]));
84        try {
85            final XPathFactory xPathFactory = XPathFactory.newInstance();
86            final XPath xPath = xPathFactory.newXPath();
87            isLibrary = (Boolean) xPath.evaluate("boolean(/manifest/application)", androidManifest,
88                    XPathConstants.BOOLEAN);
89            applicationPackage = xPath.evaluate("string(/manifest/@package)", androidManifest);
90            final Double minSdkNumber = (Double) xPath.evaluate(
91                    "number(/manifest/uses-sdk/@android:minSdkVersion)", androidManifest,
92                    XPathConstants.NUMBER);
93            minSdk = minSdkNumber == null ? 1 : minSdkNumber.intValue();
94        } catch (XPathExpressionException e) {
95            e.printStackTrace();
96            System.exit(6);
97            return;
98        }
99        final File srcDir = new File(args[SRC_INDEX], APP_SUBPATH);
100        if (!makeTargetDir(srcDir)) {
101            System.err.println("Could not create source directory " + srcDir);
102            System.exit(2);
103        }
104        final File resTarget = new File(args[RES_OUT_INDEX]);
105        if (!makeTargetDir(resTarget)) {
106            System.err.println("Could not create resource directory: " + resTarget);
107            System.exit(4);
108        }
109        final File xmlDir = new File(args[XML_INDEX]);
110        if (!makeTargetDir(xmlDir)) {
111            System.err.println("Could not create xml output directory: " + xmlDir);
112            System.exit(5);
113        }
114        System.out.println("Application Package: " + applicationPackage);
115        System.out.println("Minimum SDK: " + minSdk);
116        System.out.println("Target Resources: " + resTarget.getAbsolutePath());
117        System.out.println("Target Source Dir: " + srcDir.getAbsolutePath());
118        System.out.println("Target XML Dir: " + xmlDir.getAbsolutePath());
119
120        boolean foundSomeResources = false;
121        for (int i = RES_IN_INDEX; i < args.length; i++) {
122            final File resDir = new File(args[i]);
123            if (!resDir.exists()) {
124                System.err.println("Could not find resource directory: " + resDir);
125            } else {
126                System.out.println("Source Resources: " + resDir.getAbsolutePath());
127                try {
128                    FileUtils.copyDirectory(resDir, resTarget);
129                    addFromFile(resDir, resTarget);
130                    foundSomeResources = true;
131                } catch (IOException e) {
132                    System.err.println("Could not copy resources from " + resDir + " to " + resTarget +
133                            ": " + e.getLocalizedMessage());
134                    System.exit(3);
135                }
136            }
137        }
138
139        if (!foundSomeResources) {
140            System.err.println("No resource directories were found.");
141            System.exit(7);
142        }
143        processLayoutFiles(applicationPackage, resTarget, srcDir, xmlDir, minSdk,
144                isLibrary);
145    }
146
147    private static Document readAndroidManifest(File manifest) {
148        try {
149            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
150            DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
151            return documentBuilder.parse(manifest);
152        } catch (Exception e) {
153            System.err.println("Could not load Android Manifest from " +
154                    manifest.getAbsolutePath() + ": " + e.getLocalizedMessage());
155            System.exit(8);
156            return null;
157        }
158    }
159
160    private static void processLayoutFiles(String applicationPackage, File resTarget, File srcDir,
161            File xmlDir, int minSdk, boolean isLibrary) {
162        ArrayList<File> resourceFolders = new ArrayList<File>();
163        resourceFolders.add(resTarget);
164        MakeFileWriter makeFileWriter = new MakeFileWriter(srcDir);
165        LayoutXmlProcessor xmlProcessor = new LayoutXmlProcessor(applicationPackage,
166                resourceFolders, makeFileWriter, minSdk, isLibrary);
167        try {
168            xmlProcessor.processResources();
169            xmlProcessor.writeIntermediateFile(null, xmlDir);
170            if (makeFileWriter.getErrorCount() > 0) {
171                System.exit(9);
172            }
173        } catch (Exception e) {
174            System.err.println("Error processing layout files: " + e.getLocalizedMessage());
175            System.exit(10);
176        }
177    }
178
179    private static void addFromFile(File resDir, File resTarget) {
180        for (File layoutDir : resDir.listFiles(LAYOUT_DIR_FILTER)) {
181            if (layoutDir.isDirectory()) {
182                File targetDir = new File(resTarget, layoutDir.getName());
183                for (File layoutFile : layoutDir.listFiles(XML_FILENAME_FILTER)) {
184                    File targetFile = new File(targetDir, layoutFile.getName());
185                    FileWriter appender = null;
186                    try {
187                        appender = new FileWriter(targetFile, true);
188                        appender.write("<!-- From: " + layoutFile.toURI().toString() + " -->\n");
189                    } catch (IOException e) {
190                        System.err.println("Could not update " + layoutFile + ": " +
191                                e.getLocalizedMessage());
192                    } finally {
193                        IOUtils.closeQuietly(appender);
194                    }
195                }
196            }
197        }
198    }
199
200    private static boolean makeTargetDir(File dir) {
201        if (dir.exists()) {
202            return dir.isDirectory();
203        }
204
205        return dir.mkdirs();
206    }
207
208    private static class MakeFileWriter extends JavaFileWriter {
209        private final File mSourceRoot;
210        private int mErrorCount;
211
212        public MakeFileWriter(File sourceRoot) {
213            mSourceRoot = sourceRoot;
214        }
215
216        @Override
217        public void writeToFile(String canonicalName, String contents) {
218            String fileName = canonicalName.replace('.', File.separatorChar) + ".java";
219            File sourceFile = new File(mSourceRoot, fileName);
220            FileWriter writer = null;
221            try {
222                sourceFile.getParentFile().mkdirs();
223                writer = new FileWriter(sourceFile);
224                writer.write(contents);
225            } catch (IOException e) {
226                System.err.println("Could not write to " + sourceFile + ": " +
227                        e.getLocalizedMessage());
228                mErrorCount++;
229            } finally {
230                IOUtils.closeQuietly(writer);
231            }
232        }
233
234        public int getErrorCount() {
235            return mErrorCount;
236        }
237    }
238}
239