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.Collections; 32 import java.util.HashMap; 33 import java.util.List; 34 import java.util.Map; 35 36 import javax.xml.parsers.DocumentBuilder; 37 import javax.xml.parsers.DocumentBuilderFactory; 38 import javax.xml.xpath.XPath; 39 import javax.xml.xpath.XPathConstants; 40 import javax.xml.xpath.XPathExpressionException; 41 import 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 */ 47 public 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 main(String[] args)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 readAndroidManifest(File manifest)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 processLayoutFiles(String applicationPackage, File resTarget, File srcDir, File xmlDir, int minSdk, boolean isLibrary)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 addFromFile(File resDir, File resTarget)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 makeTargetDir(File dir)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 MakeFileWriter(File sourceRoot)238 public MakeFileWriter(File sourceRoot) { 239 mSourceRoot = sourceRoot; 240 } 241 242 @Override writeToFile(String canonicalName, String contents)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 getErrorCount()260 public int getErrorCount() { 261 return mErrorCount; 262 } 263 } 264 } 265