MakeCopy.java revision b6887f1479c3ecec38a7989748ef33de1fbcd973
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.store.ResourceBundle.LayoutFileBundle;
23import android.databinding.tool.util.GenerationalClassUtil;
24import android.databinding.tool.writer.JavaFileWriter;
25
26import java.io.File;
27import java.io.FileWriter;
28import java.io.FilenameFilter;
29import java.io.IOException;
30import java.util.ArrayList;
31import java.util.HashMap;
32import java.util.List;
33import java.util.Map;
34
35import javax.xml.parsers.DocumentBuilder;
36import javax.xml.parsers.DocumentBuilderFactory;
37import javax.xml.xpath.XPath;
38import javax.xml.xpath.XPathConstants;
39import javax.xml.xpath.XPathExpressionException;
40import javax.xml.xpath.XPathFactory;
41
42/**
43 * This class is used by make to copy resources to an intermediate directory and start processing
44 * them. When aapt takes over, this can be easily extracted to a short script.
45 */
46public class MakeCopy {
47    private static final int MANIFEST_INDEX = 0;
48    private static final int SRC_INDEX = 1;
49    private static final int XML_INDEX = 2;
50    private static final int RES_OUT_INDEX = 3;
51    private static final int RES_IN_INDEX = 4;
52
53    private static final String APP_SUBPATH = LayoutXmlProcessor.RESOURCE_BUNDLE_PACKAGE
54            .replace('.', File.separatorChar);
55    private static final FilenameFilter LAYOUT_DIR_FILTER = new FilenameFilter() {
56        @Override
57        public boolean accept(File dir, String name) {
58            return name.toLowerCase().startsWith("layout");
59        }
60    };
61
62    private static final FilenameFilter XML_FILENAME_FILTER = new FilenameFilter() {
63        @Override
64        public boolean accept(File dir, String name) {
65            return name.toLowerCase().endsWith(".xml");
66        }
67    };
68
69    public static void main(String[] args) {
70        if (args.length < 5) {
71            System.out.println("required parameters: [-l] manifest adk-dir src-out-dir xml-out-dir " +
72                            "res-out-dir res-in-dir...");
73            System.out.println("Creates an android data binding class and copies resources from");
74            System.out.println("res-source to res-target and modifies binding layout files");
75            System.out.println("in res-target. Binding data is extracted into XML files");
76            System.out.println("and placed in xml-out-dir.");
77            System.out.println("  -l          indicates that this is a library");
78            System.out.println("  manifest    path to AndroidManifest.xml file");
79            System.out.println("  src-out-dir path to where generated source goes");
80            System.out.println("  xml-out-dir path to where generated binding XML goes");
81            System.out.println("  res-out-dir path to the where modified resources should go");
82            System.out.println("  res-in-dir  path to source resources \"res\" directory. One" +
83                    " or more are allowed.");
84            System.exit(1);
85        }
86        final boolean isLibrary = args[0].equals("-l");
87        final int indexOffset = isLibrary ? 1 : 0;
88        final String applicationPackage;
89        final int minSdk;
90        final Document androidManifest = readAndroidManifest(
91                new File(args[MANIFEST_INDEX + indexOffset]));
92        try {
93            final XPathFactory xPathFactory = XPathFactory.newInstance();
94            final XPath xPath = xPathFactory.newXPath();
95            applicationPackage = xPath.evaluate("string(/manifest/@package)", androidManifest);
96            final Double minSdkNumber = (Double) xPath.evaluate(
97                    "number(/manifest/uses-sdk/@android:minSdkVersion)", androidManifest,
98                    XPathConstants.NUMBER);
99            minSdk = minSdkNumber == null ? 1 : minSdkNumber.intValue();
100        } catch (XPathExpressionException e) {
101            e.printStackTrace();
102            System.exit(6);
103            return;
104        }
105        final File srcDir = new File(args[SRC_INDEX + indexOffset], APP_SUBPATH);
106        if (!makeTargetDir(srcDir)) {
107            System.err.println("Could not create source directory " + srcDir);
108            System.exit(2);
109        }
110        final File resTarget = new File(args[RES_OUT_INDEX + indexOffset]);
111        if (!makeTargetDir(resTarget)) {
112            System.err.println("Could not create resource directory: " + resTarget);
113            System.exit(4);
114        }
115        final File xmlDir = new File(args[XML_INDEX + indexOffset]);
116        if (!makeTargetDir(xmlDir)) {
117            System.err.println("Could not create xml output directory: " + xmlDir);
118            System.exit(5);
119        }
120        System.out.println("Application Package: " + applicationPackage);
121        System.out.println("Minimum SDK: " + minSdk);
122        System.out.println("Target Resources: " + resTarget.getAbsolutePath());
123        System.out.println("Target Source Dir: " + srcDir.getAbsolutePath());
124        System.out.println("Target XML Dir: " + xmlDir.getAbsolutePath());
125        System.out.println("Library? " + isLibrary);
126
127        boolean foundSomeResources = false;
128        for (int i = RES_IN_INDEX + indexOffset; i < args.length; i++) {
129            final File resDir = new File(args[i]);
130            if (!resDir.exists()) {
131                System.out.println("Could not find resource directory: " + resDir);
132            } else {
133                System.out.println("Source Resources: " + resDir.getAbsolutePath());
134                try {
135                    FileUtils.copyDirectory(resDir, resTarget);
136                    addFromFile(resDir, resTarget);
137                    foundSomeResources = true;
138                } catch (IOException e) {
139                    System.err.println("Could not copy resources from " + resDir + " to " + resTarget +
140                            ": " + e.getLocalizedMessage());
141                    System.exit(3);
142                }
143            }
144        }
145
146        if (!foundSomeResources) {
147            System.err.println("No resource directories were found.");
148            System.exit(7);
149        }
150        processLayoutFiles(applicationPackage, resTarget, srcDir, xmlDir, minSdk,
151                isLibrary);
152    }
153
154    private static Document readAndroidManifest(File manifest) {
155        try {
156            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
157            DocumentBuilder documentBuilder = dbf.newDocumentBuilder();
158            return documentBuilder.parse(manifest);
159        } catch (Exception e) {
160            System.err.println("Could not load Android Manifest from " +
161                    manifest.getAbsolutePath() + ": " + e.getLocalizedMessage());
162            System.exit(8);
163            return null;
164        }
165    }
166
167    private static void processLayoutFiles(String applicationPackage, File resTarget, File srcDir,
168            File xmlDir, int minSdk, boolean isLibrary) {
169        ArrayList<File> resourceFolders = new ArrayList<File>();
170        resourceFolders.add(resTarget);
171        MakeFileWriter makeFileWriter = new MakeFileWriter(srcDir);
172        LayoutXmlProcessor xmlProcessor = new LayoutXmlProcessor(applicationPackage,
173                resourceFolders, makeFileWriter, minSdk, isLibrary);
174        try {
175            xmlProcessor.processResources(minSdk);
176            xmlProcessor.writeLayoutInfoFiles(xmlDir);
177            // TODO Looks like make does not support excluding from libs ?
178            xmlProcessor.writeInfoClass(null, xmlDir, null);
179            Map<String, List<LayoutFileBundle>> bundles =
180                    xmlProcessor.getResourceBundle().getLayoutBundles();
181            if (isLibrary) {
182                for (String name : bundles.keySet()) {
183                    LayoutFileBundle layoutFileBundle = bundles.get(name).get(0);
184                    String pkgName = layoutFileBundle.getBindingClassPackage().replace('.', '/');
185                    System.err.println(pkgName + '/' + layoutFileBundle.getBindingClassName() +
186                        ".class");
187                }
188            }
189            if (makeFileWriter.getErrorCount() > 0) {
190                System.exit(9);
191            }
192        } catch (Exception e) {
193            System.err.println("Error processing layout files: " + e.getLocalizedMessage());
194            System.exit(10);
195        }
196    }
197
198    private static void addFromFile(File resDir, File resTarget) {
199        for (File layoutDir : resDir.listFiles(LAYOUT_DIR_FILTER)) {
200            if (layoutDir.isDirectory()) {
201                File targetDir = new File(resTarget, layoutDir.getName());
202                for (File layoutFile : layoutDir.listFiles(XML_FILENAME_FILTER)) {
203                    File targetFile = new File(targetDir, layoutFile.getName());
204                    FileWriter appender = null;
205                    try {
206                        appender = new FileWriter(targetFile, true);
207                        appender.write("<!-- From: " + layoutFile.toURI().toString() + " -->\n");
208                    } catch (IOException e) {
209                        System.err.println("Could not update " + layoutFile + ": " +
210                                e.getLocalizedMessage());
211                    } finally {
212                        IOUtils.closeQuietly(appender);
213                    }
214                }
215            }
216        }
217    }
218
219    private static boolean makeTargetDir(File dir) {
220        if (dir.exists()) {
221            return dir.isDirectory();
222        }
223
224        return dir.mkdirs();
225    }
226
227    private static class MakeFileWriter extends JavaFileWriter {
228        private final File mSourceRoot;
229        private int mErrorCount;
230
231        public MakeFileWriter(File sourceRoot) {
232            mSourceRoot = sourceRoot;
233        }
234
235        @Override
236        public void writeToFile(String canonicalName, String contents) {
237            String fileName = canonicalName.replace('.', File.separatorChar) + ".java";
238            File sourceFile = new File(mSourceRoot, fileName);
239            FileWriter writer = null;
240            try {
241                sourceFile.getParentFile().mkdirs();
242                writer = new FileWriter(sourceFile);
243                writer.write(contents);
244            } catch (IOException e) {
245                System.err.println("Could not write to " + sourceFile + ": " +
246                        e.getLocalizedMessage());
247                mErrorCount++;
248            } finally {
249                IOUtils.closeQuietly(writer);
250            }
251        }
252
253        public int getErrorCount() {
254            return mErrorCount;
255        }
256    }
257}
258