• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package android.databinding.tool;
17 
18 import org.apache.commons.io.FileUtils;
19 import org.apache.commons.io.IOUtils;
20 import org.w3c.dom.Document;
21 
22 import android.databinding.tool.store.ResourceBundle.LayoutFileBundle;
23 import android.databinding.tool.util.GenerationalClassUtil;
24 import android.databinding.tool.writer.JavaFileWriter;
25 
26 import java.io.File;
27 import java.io.FileWriter;
28 import java.io.FilenameFilter;
29 import java.io.IOException;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 
35 import javax.xml.parsers.DocumentBuilder;
36 import javax.xml.parsers.DocumentBuilderFactory;
37 import javax.xml.xpath.XPath;
38 import javax.xml.xpath.XPathConstants;
39 import javax.xml.xpath.XPathExpressionException;
40 import 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  */
46 public 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 
main(String[] args)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 
readAndroidManifest(File manifest)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 
processLayoutFiles(String applicationPackage, File resTarget, File srcDir, File xmlDir, int minSdk, boolean isLibrary)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 
addFromFile(File resDir, File resTarget)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 
makeTargetDir(File dir)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 
MakeFileWriter(File sourceRoot)231         public MakeFileWriter(File sourceRoot) {
232             mSourceRoot = sourceRoot;
233         }
234 
235         @Override
writeToFile(String canonicalName, String contents)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 
getErrorCount()253         public int getErrorCount() {
254             return mErrorCount;
255         }
256     }
257 }
258