Fix ClassScanner and re-enable CodeInspectionTest tests
This fixes the code in ClassScanner for finding all classes in a given package to not depend on directory entries in the .jar files generated by the build system. This dependency caused our tests in CodeInspepectionTest.java to fail when this CL: https://android-review.googlesource.com/#/c/platform/build/+/456418/ stopped adding directory entries in the .jar files generated by the build process. Instead of depending on directories being present in the list of resources provided by the classloader, this CL switches to using Guava's ClassPath class to enumerate all loadable classes and filter them to the ones in the package of interest. Change-Id: I583919096450b61d4816256be280e2f5f1ce2316 Fixes: 64840107 Test: make RunSettingsRoboTests
This commit is contained in:
@@ -16,50 +16,47 @@
|
|||||||
|
|
||||||
package com.android.settings.core.codeinspection;
|
package com.android.settings.core.codeinspection;
|
||||||
|
|
||||||
import java.io.File;
|
import com.google.common.reflect.ClassPath;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.JarURLConnection;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.regex.Matcher;
|
||||||
import java.util.jar.JarFile;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scans and builds all classes in current classloader.
|
* Scans and builds all classes in current classloader.
|
||||||
*/
|
*/
|
||||||
public class ClassScanner {
|
public class ClassScanner {
|
||||||
|
|
||||||
private static final String CLASS_SUFFIX = ".class";
|
|
||||||
|
|
||||||
public List<Class<?>> getClassesForPackage(String packageName)
|
public List<Class<?>> getClassesForPackage(String packageName)
|
||||||
throws ClassNotFoundException {
|
throws ClassNotFoundException {
|
||||||
final List<Class<?>> classes = new ArrayList<>();
|
final List<Class<?>> classes = new ArrayList<>();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final Enumeration<URL> resources = Thread.currentThread().getContextClassLoader()
|
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
|
||||||
.getResources(packageName.replace('.', '/'));
|
ClassPath classPath = ClassPath.from(classLoader);
|
||||||
if (!resources.hasMoreElements()) {
|
|
||||||
return classes;
|
|
||||||
}
|
|
||||||
URL url = resources.nextElement();
|
|
||||||
while (url != null) {
|
|
||||||
final URLConnection connection = url.openConnection();
|
|
||||||
|
|
||||||
if (connection instanceof JarURLConnection) {
|
// Some anonymous classes don't return true when calling isAnonymousClass(), but they
|
||||||
loadClassFromJar((JarURLConnection) connection, packageName,
|
// always seem to be nested anonymous classes like com.android.settings.Foo$1$2. In
|
||||||
classes);
|
// general we don't want any anonymous classes so we just filter these out by searching
|
||||||
} else {
|
// for $[0-9] in the name.
|
||||||
loadClassFromDirectory(new File(URLDecoder.decode(url.getPath(), "UTF-8")),
|
Pattern anonymousClassPattern = Pattern.compile(".*\\$\\d+.*");
|
||||||
packageName, classes);
|
Matcher anonymousClassMatcher = anonymousClassPattern.matcher("");
|
||||||
}
|
|
||||||
if (resources.hasMoreElements()) {
|
for (ClassPath.ClassInfo info : classPath.getAllClasses()) {
|
||||||
url = resources.nextElement();
|
if (info.getPackageName().startsWith(packageName)) {
|
||||||
} else {
|
try {
|
||||||
break;
|
Class clazz = classLoader.loadClass(info.getName());
|
||||||
|
if (clazz.isAnonymousClass() || anonymousClassMatcher.reset(
|
||||||
|
clazz.getName()).matches()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
classes.add(clazz);
|
||||||
|
} catch (NoClassDefFoundError e) {
|
||||||
|
// do nothing. this class hasn't been found by the
|
||||||
|
// loader, and we don't care.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
@@ -68,63 +65,4 @@ public class ClassScanner {
|
|||||||
return classes;
|
return classes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadClassFromDirectory(File directory, String packageName, List<Class<?>> classes)
|
|
||||||
throws ClassNotFoundException {
|
|
||||||
if (directory.exists() && directory.isDirectory()) {
|
|
||||||
final String[] files = directory.list();
|
|
||||||
|
|
||||||
for (final String file : files) {
|
|
||||||
if (file.endsWith(CLASS_SUFFIX)) {
|
|
||||||
try {
|
|
||||||
classes.add(Class.forName(
|
|
||||||
packageName + '.' + file.substring(0, file.length() - 6),
|
|
||||||
false /* init */,
|
|
||||||
Thread.currentThread().getContextClassLoader()));
|
|
||||||
} catch (NoClassDefFoundError e) {
|
|
||||||
// do nothing. this class hasn't been found by the
|
|
||||||
// loader, and we don't care.
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
final File tmpDirectory = new File(directory, file);
|
|
||||||
if (tmpDirectory.isDirectory()) {
|
|
||||||
loadClassFromDirectory(tmpDirectory, packageName + "." + file, classes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadClassFromJar(JarURLConnection connection, String packageName,
|
|
||||||
List<Class<?>> classes) throws ClassNotFoundException, IOException {
|
|
||||||
final JarFile jarFile = connection.getJarFile();
|
|
||||||
final Enumeration<JarEntry> entries = jarFile.entries();
|
|
||||||
String name;
|
|
||||||
if (!entries.hasMoreElements()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
JarEntry jarEntry = entries.nextElement();
|
|
||||||
while (jarEntry != null) {
|
|
||||||
name = jarEntry.getName();
|
|
||||||
|
|
||||||
if (name.contains(CLASS_SUFFIX)) {
|
|
||||||
name = name.substring(0, name.length() - CLASS_SUFFIX.length()).replace('/', '.');
|
|
||||||
|
|
||||||
if (name.startsWith(packageName)) {
|
|
||||||
try {
|
|
||||||
classes.add(Class.forName(name,
|
|
||||||
false /* init */,
|
|
||||||
Thread.currentThread().getContextClassLoader()));
|
|
||||||
} catch (NoClassDefFoundError e) {
|
|
||||||
// do nothing. this class hasn't been found by the
|
|
||||||
// loader, and we don't care.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entries.hasMoreElements()) {
|
|
||||||
jarEntry = entries.nextElement();
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -44,14 +44,12 @@ public class CodeInspectionTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
mClasses = new ClassScanner().getClassesForPackage(CodeInspector.PACKAGE_NAME);
|
mClasses = new ClassScanner().getClassesForPackage(CodeInspector.PACKAGE_NAME);
|
||||||
// Disabled temporarily - see b/64840107
|
assertThat(mClasses).isNotEmpty();
|
||||||
//assertThat(mClasses).isNotEmpty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void runCodeInspections() {
|
public void runCodeInspections() {
|
||||||
// Disabled temporarily - see b/64840107
|
new InstrumentableFragmentCodeInspector(mClasses).run();
|
||||||
// new InstrumentableFragmentCodeInspector(mClasses).run();
|
new SearchIndexProviderCodeInspector(mClasses).run();
|
||||||
// new SearchIndexProviderCodeInspector(mClasses).run();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user