/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.support.test.internal.runner; import dalvik.system.DexFile; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; /** * Finds class entries in apks. *

* Adapted from tools/tradefederation/..ClassPathScanner */ class ClassPathScanner { /** * A filter for classpath entry paths *

* Patterned after {@link java.io.FileFilter} */ public static interface ClassNameFilter { /** * Tests whether or not the specified abstract pathname should be included in a class path * entry list. * * @param pathName the relative path of the class path entry */ boolean accept(String className); } /** * A {@link ClassNameFilter} that accepts all class names. */ public static class AcceptAllFilter implements ClassNameFilter { /** * {@inheritDoc} */ @Override public boolean accept(String className) { return true; } } /** * A {@link ClassNameFilter} that chains one or more filters together */ public static class ChainedClassNameFilter implements ClassNameFilter { private final List mFilters = new ArrayList(); public void add(ClassNameFilter filter) { mFilters.add(filter); } public void addAll(ClassNameFilter... filters) { mFilters.addAll(Arrays.asList(filters)); } /** * {@inheritDoc} */ @Override public boolean accept(String className) { for (ClassNameFilter filter : mFilters) { if (!filter.accept(className)) { return false; } } return true; } } /** * A {@link ClassNameFilter} that rejects inner classes. */ public static class ExternalClassNameFilter implements ClassNameFilter { /** * {@inheritDoc} */ @Override public boolean accept(String pathName) { return !pathName.contains("$"); } } /** * A {@link ClassNameFilter} that only accepts package names within the given namespace. */ public static class InclusivePackageNameFilter implements ClassNameFilter { private final String mPkgName; InclusivePackageNameFilter(String pkgName) { if (!pkgName.endsWith(".")) { mPkgName = String.format("%s.", pkgName); } else { mPkgName = pkgName; } } /** * {@inheritDoc} */ @Override public boolean accept(String pathName) { return pathName.startsWith(mPkgName); } } /** * A {@link ClassNameFilter} that only rejects a given package names within the given namespace. */ public static class ExcludePackageNameFilter implements ClassNameFilter { private final String mPkgName; ExcludePackageNameFilter(String pkgName) { if (!pkgName.endsWith(".")) { mPkgName = String.format("%s.", pkgName); } else { mPkgName = pkgName; } } /** * {@inheritDoc} */ @Override public boolean accept(String pathName) { return !pathName.startsWith(mPkgName); } } private Set mApkPaths = new HashSet(); public ClassPathScanner(String... apkPaths) { for (String apkPath : apkPaths) { mApkPaths.add(apkPath); } } /** * Gets the names of all entries contained in given apk file, that match given filter. * @throws IOException */ private void addEntriesFromApk(Set entryNames, String apkPath, ClassNameFilter filter) throws IOException { DexFile dexFile = null; try { dexFile = new DexFile(apkPath); Enumeration apkClassNames = getDexEntries(dexFile); while (apkClassNames.hasMoreElements()) { String apkClassName = apkClassNames.nextElement(); if (filter.accept(apkClassName)) { entryNames.add(apkClassName); } } } finally { if (dexFile != null) { dexFile.close(); } } } /** * Retrieves the entry names from given {@link DexFile}. *

* Exposed for unit testing. * * @param dexFile * @return {@link Enumeration} of {@link String}s */ Enumeration getDexEntries(DexFile dexFile) { return dexFile.entries(); } /** * Retrieves set of classpath entries that match given {@link ClassNameFilter}. * @throws IOException */ public Set getClassPathEntries(ClassNameFilter filter) throws IOException { // use LinkedHashSet for predictable order Set entryNames = new LinkedHashSet(); for (String apkPath : mApkPaths) { addEntriesFromApk(entryNames, apkPath, filter); } return entryNames; } }