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