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