diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 8a4b5b345a170..12acb094c1517 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -33,6 +33,8 @@ built_core_classes := $(call java-lib-files,core) built_ext_dep := $(call java-lib-deps,ext) built_ext_classes := $(call java-lib-files,ext) +built_ext_data := $(call intermediates-dir-for, \ + JAVA_LIBRARIES,ext,,COMMON)/javalib.jar built_layoutlib_create_jar := $(call intermediates-dir-for, \ JAVA_LIBRARIES,layoutlib_create,HOST)/javalib.jar @@ -60,7 +62,8 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $@ \ $(built_core_classes) \ $(built_framework_classes) \ - $(built_ext_classes) + $(built_ext_classes) \ + $(built_ext_data) $(hide) ls -l $(built_framework_classes) diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index 1572a4034432a..9a31705d0a3a9 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -29,6 +29,7 @@ import org.objectweb.asm.signature.SignatureVisitor; import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; @@ -60,6 +61,9 @@ public class AsmAnalyzer { private final String[] mIncludeGlobs; /** The set of classes to exclude.*/ private final Set mExcludedClasses; + /** Glob patterns of files to keep as is. */ + private final String[] mIncludeFileGlobs; + /** Copy these files into the output as is. */ /** * Creates a new analyzer. @@ -70,15 +74,19 @@ public class AsmAnalyzer { * @param deriveFrom Keep all classes that derive from these one (these included). * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*" * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is) + * @param includeFileGlobs Glob patterns of files which are kept as is. This is only for files + * not ending in .class. */ public AsmAnalyzer(Log log, List osJarPath, AsmGenerator gen, - String[] deriveFrom, String[] includeGlobs, Set excludeClasses) { + String[] deriveFrom, String[] includeGlobs, Set excludeClasses, + String[] includeFileGlobs) { mLog = log; mGen = gen; mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList(); mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0]; mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0]; mExcludedClasses = excludeClasses; + mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0]; } /** @@ -86,7 +94,11 @@ public AsmAnalyzer(Log log, List osJarPath, AsmGenerator gen, * Fills the generator with classes & dependencies found. */ public void analyze() throws IOException, LogAbortException { - Map zipClasses = parseZip(mOsSourceJar); + + TreeMap zipClasses = new TreeMap(); + Map filesFound = new TreeMap(); + + parseZip(mOsSourceJar, zipClasses, filesFound); mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), mOsSourceJar.size() > 1 ? "s" : ""); @@ -96,15 +108,29 @@ public void analyze() throws IOException, LogAbortException { if (mGen != null) { mGen.setKeep(found); mGen.setDeps(deps); + mGen.setCopyFiles(filesFound); } } /** - * Parses a JAR file and returns a list of all classes founds using a map - * class name => ASM ClassReader. Class names are in the form "android.view.View". + * Parses a JAR file and adds all the classes found to classes + * and all other files to filesFound. + * + * @param classes The map of class name => ASM ClassReader. Class names are + * in the form "android.view.View". + * @param fileFound The map of file name => InputStream. The file name is + * in the form "android/data/dataFile". */ - Map parseZip(List jarPathList) throws IOException { - TreeMap classes = new TreeMap(); + void parseZip(List jarPathList, Map classes, + Map filesFound) throws IOException { + if (classes == null || filesFound == null) { + return; + } + + Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length]; + for (int i = 0; i < mIncludeFileGlobs.length; ++i) { + includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]); + } for (String jarPath : jarPathList) { ZipFile zip = new ZipFile(jarPath); @@ -116,11 +142,17 @@ Map parseZip(List jarPathList) throws IOException { ClassReader cr = new ClassReader(zip.getInputStream(entry)); String className = classReaderToClassName(cr); classes.put(className, cr); + } else { + for (int i = 0; i < includeFilePatterns.length; ++i) { + if (includeFilePatterns[i].matcher(entry.getName()).matches()) { + filesFound.put(entry.getName(), zip.getInputStream(entry)); + break; + } + } } } } - return classes; } /** @@ -202,7 +234,19 @@ ClassReader findClass(String className, Map zipClasses, */ void findGlobs(String globPattern, Map zipClasses, Map inOutFound) throws LogAbortException { - // transforms the glob pattern in a regexp: + + Pattern regexp = getPatternFromGlob(globPattern); + + for (Entry entry : zipClasses.entrySet()) { + String class_name = entry.getKey(); + if (regexp.matcher(class_name).matches()) { + findClass(class_name, zipClasses, inOutFound); + } + } + } + + Pattern getPatternFromGlob(String globPattern) { + // transforms the glob pattern in a regexp: // - escape "." with "\." // - replace "*" by "[^.]*" // - escape "$" with "\$" @@ -216,14 +260,7 @@ void findGlobs(String globPattern, Map zipClasses, globPattern = globPattern.replaceAll("@", ".*"); globPattern += "$"; - Pattern regexp = Pattern.compile(globPattern); - - for (Entry entry : zipClasses.entrySet()) { - String class_name = entry.getKey(); - if (regexp.matcher(class_name).matches()) { - findClass(class_name, zipClasses, inOutFound); - } - } + return Pattern.compile(globPattern); } /** diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index b10256127ae88..207d8ae7b2d9e 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -20,6 +20,7 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -52,6 +53,8 @@ public class AsmGenerator { private Map mKeep; /** All dependencies that must be completely stubbed. */ private Map mDeps; + /** All files that are to be copied as-is. */ + private Map mCopyFiles; /** Counter of number of classes renamed during transform. */ private int mRenameCount; /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */ @@ -195,6 +198,11 @@ public void setDeps(Map deps) { mDeps = deps; } + /** Sets the map of files to output as-is. */ + public void setCopyFiles(Map copyFiles) { + mCopyFiles = copyFiles; + } + /** Gets the map of classes to output as-is, except if they have native methods */ public Map getKeep() { return mKeep; @@ -205,6 +213,11 @@ public Map getDeps() { return mDeps; } + /** Gets the map of files to output as-is. */ + public Map getCopyFiles() { + return mCopyFiles; + } + /** Generates the final JAR */ public void generate() throws FileNotFoundException, IOException { TreeMap all = new TreeMap(); @@ -232,6 +245,15 @@ public void generate() throws FileNotFoundException, IOException { all.put(name, b); } + for (Entry entry : mCopyFiles.entrySet()) { + try { + byte[] b = inputStreamToByteArray(entry.getValue()); + all.put(entry.getKey(), b); + } catch (IOException e) { + // Ignore. + } + + } mLog.info("# deps classes: %d", mDeps.size()); mLog.info("# keep classes: %d", mKeep.size()); mLog.info("# renamed : %d", mRenameCount); @@ -381,4 +403,13 @@ boolean hasNativeMethods(ClassReader cr) { return cv.hasNativeMethods(); } + private byte[] inputStreamToByteArray(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[8192]; // 8KB + int n; + while ((n = is.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, n); + } + return buffer.toByteArray(); + } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index ee501d234d01f..a79fba19d2161 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -115,7 +115,10 @@ private static int createLayoutLib(String osDestJar, ArrayList osJarPath "android.database.ContentObserver", // for Digital clock "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute }, - excludeClasses); + excludeClasses, + new String[] { + "com/android/i18n/phonenumbers/data/*", + }); aa.analyze(); agen.generate(); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java index 005fc9dadab41..7ec0d389be872 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java @@ -29,6 +29,7 @@ import org.objectweb.asm.ClassReader; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashSet; @@ -55,8 +56,10 @@ public void setUp() throws Exception { Set excludeClasses = new HashSet(1); excludeClasses.add("java.lang.JavaClass"); - mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, - null /* deriveFrom */, null /* includeGlobs */, excludeClasses); + + String[] includeFiles = new String[]{"mock_android/data/data*"}; + mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, null /* deriveFrom */, + null /* includeGlobs */, excludeClasses, includeFiles); } @After @@ -65,7 +68,11 @@ public void tearDown() throws Exception { @Test public void testParseZip() throws IOException { - Map map = mAa.parseZip(mOsJarPath); + + Map map = new TreeMap(); + Map filesFound = new TreeMap(); + + mAa.parseZip(mOsJarPath, map, filesFound); assertArrayEquals(new String[] { "java.lang.JavaClass", @@ -86,11 +93,17 @@ public void testParseZip() throws IOException { "mock_android.widget.TableLayout$LayoutParams" }, map.keySet().toArray()); + assertArrayEquals(new String[] {"mock_android/data/dataFile"}, + filesFound.keySet().toArray()); } @Test public void testFindClass() throws IOException, LogAbortException { - Map zipClasses = mAa.parseZip(mOsJarPath); + + Map zipClasses = new TreeMap(); + Map filesFound = new TreeMap(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap found = new TreeMap(); ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams", @@ -105,7 +118,11 @@ public void testFindClass() throws IOException, LogAbortException { @Test public void testFindGlobs() throws IOException, LogAbortException { - Map zipClasses = mAa.parseZip(mOsJarPath); + + Map zipClasses = new TreeMap(); + Map filesFound = new TreeMap(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap found = new TreeMap(); // this matches classes, a package match returns nothing @@ -164,7 +181,11 @@ public void testFindGlobs() throws IOException, LogAbortException { @Test public void testFindClassesDerivingFrom() throws LogAbortException, IOException { - Map zipClasses = mAa.parseZip(mOsJarPath); + + Map zipClasses = new TreeMap(); + Map filesFound = new TreeMap(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap found = new TreeMap(); mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found); @@ -186,7 +207,11 @@ public void testFindClassesDerivingFrom() throws LogAbortException, IOException @Test public void testDependencyVisitor() throws IOException, LogAbortException { - Map zipClasses = mAa.parseZip(mOsJarPath); + + Map zipClasses = new TreeMap(); + Map filesFound = new TreeMap(); + + mAa.parseZip(mOsJarPath, zipClasses, filesFound); TreeMap keep = new TreeMap(); TreeMap new_keep = new TreeMap(); TreeMap in_deps = new TreeMap(); diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java index 8a27173181a30..0dbc2387b1287 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; @@ -131,7 +132,8 @@ public String[] getDeleteReturns() { new String[] { // include classes "**" }, - new HashSet(0) /* excluded classes */); + new HashSet(0) /* excluded classes */, + new String[]{} /* include files */); aa.analyze(); agen.generate(); @@ -195,10 +197,15 @@ public String[] getDeleteReturns() { new String[] { // include classes "**" }, - new HashSet(1)); + new HashSet(1), + new String[] { /* include files */ + "mock_android/data/data*" + }); aa.analyze(); agen.generate(); - Map output = parseZip(mOsDestJar); + Map output = new TreeMap(); + Map filesFound = new TreeMap(); + parseZip(mOsDestJar, output, filesFound); boolean injectedClassFound = false; for (ClassReader cr: output.values()) { TestClassVisitor cv = new TestClassVisitor(); @@ -206,10 +213,13 @@ public String[] getDeleteReturns() { injectedClassFound |= cv.mInjectedClassFound; } assertTrue(injectedClassFound); + assertArrayEquals(new String[] {"mock_android/data/dataFile"}, + filesFound.keySet().toArray()); } - private Map parseZip(String jarPath) throws IOException { - TreeMap classes = new TreeMap(); + private void parseZip(String jarPath, + Map classes, + Map filesFound) throws IOException { ZipFile zip = new ZipFile(jarPath); Enumeration entries = zip.entries(); @@ -220,10 +230,11 @@ private Map parseZip(String jarPath) throws IOException { ClassReader cr = new ClassReader(zip.getInputStream(entry)); String className = classReaderToClassName(cr); classes.put(className, cr); + } else { + filesFound.put(entry.getName(), zip.getInputStream(entry)); } } - return classes; } private String classReaderToClassName(ClassReader classReader) { diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar index 60d8efb1bb997..8dd04812a866a 100644 Binary files a/tools/layoutlib/create/tests/data/mock_android.jar and b/tools/layoutlib/create/tests/data/mock_android.jar differ diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile new file mode 100644 index 0000000000000..ab29fbe449cf9 --- /dev/null +++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/anotherDataFile @@ -0,0 +1 @@ +A simple data file that should *not* be copied to the output jar. \ No newline at end of file diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile new file mode 100644 index 0000000000000..9b01893ebab37 --- /dev/null +++ b/tools/layoutlib/create/tests/mock_data/mock_android/data/dataFile @@ -0,0 +1 @@ +A simple data file that should be copied to the output jar unchanged. \ No newline at end of file