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