1 /* 2 * Copyright (C) 2012 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 17 package com.android.test.runner; 18 19 import dalvik.system.DexFile; 20 21 import java.io.IOException; 22 import java.util.Enumeration; 23 import java.util.HashSet; 24 import java.util.LinkedHashSet; 25 import java.util.Set; 26 27 /** 28 * Finds class entries in apks. 29 * <p/> 30 * Adapted from tools/tradefederation/..ClassPathScanner 31 */ 32 class ClassPathScanner { 33 34 /** 35 * A filter for classpath entry paths 36 * <p/> 37 * Patterned after {@link java.io.FileFilter} 38 */ 39 public static interface ClassNameFilter { 40 /** 41 * Tests whether or not the specified abstract pathname should be included in a class path 42 * entry list. 43 * 44 * @param pathName the relative path of the class path entry 45 */ 46 boolean accept(String className); 47 } 48 49 /** 50 * A {@link ClassNameFilter} that accepts all class names. 51 */ 52 public static class AcceptAllFilter implements ClassNameFilter { 53 54 /** 55 * {@inheritDoc} 56 */ 57 @Override 58 public boolean accept(String className) { 59 return true; 60 } 61 62 } 63 64 /** 65 * A {@link ClassNameFilter} that chains one or more filters together 66 */ 67 public static class ChainedClassNameFilter implements ClassNameFilter { 68 private final ClassNameFilter[] mFilters; 69 70 public ChainedClassNameFilter(ClassNameFilter... filters) { 71 mFilters = filters; 72 } 73 74 /** 75 * {@inheritDoc} 76 */ 77 @Override 78 public boolean accept(String className) { 79 for (ClassNameFilter filter : mFilters) { 80 if (!filter.accept(className)) { 81 return false; 82 } 83 } 84 return true; 85 } 86 } 87 88 /** 89 * A {@link ClassNameFilter} that rejects inner classes. 90 */ 91 public static class ExternalClassNameFilter implements ClassNameFilter { 92 /** 93 * {@inheritDoc} 94 */ 95 @Override 96 public boolean accept(String pathName) { 97 return !pathName.contains("$"); 98 } 99 } 100 101 /** 102 * A {@link ClassNameFilter} that only accepts package names within the given namespace. 103 */ 104 public static class InclusivePackageNameFilter implements ClassNameFilter { 105 106 private final String mPkgName; 107 108 InclusivePackageNameFilter(String pkgName) { 109 if (!pkgName.endsWith(".")) { 110 mPkgName = String.format("%s.", pkgName); 111 } else { 112 mPkgName = pkgName; 113 } 114 } 115 116 /** 117 * {@inheritDoc} 118 */ 119 @Override 120 public boolean accept(String pathName) { 121 return pathName.startsWith(mPkgName); 122 } 123 } 124 125 /** 126 * A {@link ClassNameFilter} that only rejects a given package names within the given namespace. 127 */ 128 public static class ExcludePackageNameFilter implements ClassNameFilter { 129 130 private final String mPkgName; 131 132 ExcludePackageNameFilter(String pkgName) { 133 if (!pkgName.endsWith(".")) { 134 mPkgName = String.format("%s.", pkgName); 135 } else { 136 mPkgName = pkgName; 137 } 138 } 139 140 /** 141 * {@inheritDoc} 142 */ 143 @Override 144 public boolean accept(String pathName) { 145 return !pathName.startsWith(mPkgName); 146 } 147 } 148 149 private Set<String> mApkPaths = new HashSet<String>(); 150 151 public ClassPathScanner(String... apkPaths) { 152 for (String apkPath : apkPaths) { 153 mApkPaths.add(apkPath); 154 } 155 } 156 157 /** 158 * Gets the names of all entries contained in given apk file, that match given filter. 159 * @throws IOException 160 */ 161 private void addEntriesFromApk(Set<String> entryNames, String apkPath, ClassNameFilter filter) 162 throws IOException { 163 DexFile dexFile = null; 164 try { 165 dexFile = new DexFile(apkPath); 166 Enumeration<String> apkClassNames = getDexEntries(dexFile); 167 while (apkClassNames.hasMoreElements()) { 168 String apkClassName = apkClassNames.nextElement(); 169 if (filter.accept(apkClassName)) { 170 entryNames.add(apkClassName); 171 } 172 } 173 } finally { 174 if (dexFile != null) { 175 dexFile.close(); 176 } 177 } 178 } 179 180 /** 181 * Retrieves the entry names from given {@link DexFile}. 182 * <p/> 183 * Exposed for unit testing. 184 * 185 * @param dexFile 186 * @return {@link Enumeration} of {@link String}s 187 */ 188 Enumeration<String> getDexEntries(DexFile dexFile) { 189 return dexFile.entries(); 190 } 191 192 /** 193 * Retrieves set of classpath entries that match given {@link ClassNameFilter}. 194 * @throws IOException 195 */ 196 public Set<String> getClassPathEntries(ClassNameFilter filter) throws IOException { 197 // use LinkedHashSet for predictable order 198 Set<String> entryNames = new LinkedHashSet<String>(); 199 for (String apkPath : mApkPaths) { 200 addEntriesFromApk(entryNames, apkPath, filter); 201 } 202 return entryNames; 203 } 204 } 205