Bug 1192079 - Support inner classes in generated JNI wrapper; r=snorp

Currently, when we generate JNI wrapper for an inner class, the
resulting C++ class will not actually be a nested class of the enclosing
class. As a result, the class can be confusing to use. For example,
wrapping Java class GeckoThread.State results in two unrelated C++
classes, GeckoThread and State, and it'd be confusing to use State by
itself.

This patch adds support for inner classes. We start by scanning only for
top-level classes, and when processing each top-level class, we
recursively scan for inner classes through
JarClassIterator.getInnerClasses() and CodeGenerator.generateClasses().
For each Java inner classes, the resulting C++ class will be a nested
class. For example, wrapping GeckoThread.State will produce
widget::GeckoThread and widget::GeckoThread::State.
This commit is contained in:
Jim Chen 2015-08-13 00:53:39 -04:00
Родитель d1f529698f
Коммит 7595348845
5 изменённых файлов: 129 добавлений и 43 удалений

Просмотреть файл

@ -27,6 +27,10 @@ public class AnnotationProcessor {
"// will cause your build to fail.\n" +
"\n";
private static final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
private static final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
private static final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
public static void main(String[] args) {
// We expect a list of jars on the commandline. If missing, whinge about it.
if (args.length <= 1) {
@ -46,7 +50,6 @@ public class AnnotationProcessor {
// Get an iterator over the classes in the jar files given...
Iterator<ClassWithOptions> jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(args);
StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT);
headerFile.append(
"#ifndef " + getHeaderGuardName(HEADER_FILE) + "\n" +
"#define " + getHeaderGuardName(HEADER_FILE) + "\n" +
@ -57,7 +60,6 @@ public class AnnotationProcessor {
"namespace widget {\n" +
"\n");
StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
implementationFile.append(
"#include \"GeneratedJNIWrappers.h\"\n" +
"#include \"mozilla/jni/Accessors.h\"\n" +
@ -66,7 +68,6 @@ public class AnnotationProcessor {
"namespace widget {\n" +
"\n");
StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT);
nativesFile.append(
"#ifndef " + getHeaderGuardName(NATIVES_FILE) + "\n" +
"#define " + getHeaderGuardName(NATIVES_FILE) + "\n" +
@ -79,40 +80,7 @@ public class AnnotationProcessor {
"\n");
while (jarClassIterator.hasNext()) {
ClassWithOptions aClassTuple = jarClassIterator.next();
CodeGenerator generatorInstance;
// Get an iterator over the appropriately generated methods of this class
Iterator<AnnotatableEntity> methodIterator = new GeneratableElementIterator(aClassTuple.wrappedClass);
if (!methodIterator.hasNext()) {
continue;
}
generatorInstance = new CodeGenerator(aClassTuple);
// Iterate all annotated members in this class..
while (methodIterator.hasNext()) {
AnnotatableEntity aElementTuple = methodIterator.next();
switch (aElementTuple.mEntityType) {
case METHOD:
generatorInstance.generateMethod(aElementTuple);
break;
case NATIVE:
generatorInstance.generateNative(aElementTuple);
break;
case FIELD:
generatorInstance.generateField(aElementTuple);
break;
case CONSTRUCTOR:
generatorInstance.generateConstructor(aElementTuple);
break;
}
}
headerFile.append(generatorInstance.getHeaderFileContents());
implementationFile.append(generatorInstance.getWrapperFileContents());
nativesFile.append(generatorInstance.getNativesFileContents());
generateClass(jarClassIterator.next());
}
implementationFile.append(
@ -132,10 +100,52 @@ public class AnnotationProcessor {
writeOutputFile(SOURCE_FILE, implementationFile);
writeOutputFile(HEADER_FILE, headerFile);
writeOutputFile(NATIVES_FILE, nativesFile);
long e = System.currentTimeMillis();
System.out.println("Annotation processing complete in " + (e - s) + "ms");
}
private static void generateClass(final ClassWithOptions annotatedClass) {
// Get an iterator over the appropriately generated methods of this class
final GeneratableElementIterator methodIterator
= new GeneratableElementIterator(annotatedClass);
final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses();
if (!methodIterator.hasNext() && innerClasses.length == 0) {
return;
}
final CodeGenerator generatorInstance = new CodeGenerator(annotatedClass);
generatorInstance.generateClasses(innerClasses);
// Iterate all annotated members in this class..
while (methodIterator.hasNext()) {
AnnotatableEntity aElementTuple = methodIterator.next();
switch (aElementTuple.mEntityType) {
case METHOD:
generatorInstance.generateMethod(aElementTuple);
break;
case NATIVE:
generatorInstance.generateNative(aElementTuple);
break;
case FIELD:
generatorInstance.generateField(aElementTuple);
break;
case CONSTRUCTOR:
generatorInstance.generateConstructor(aElementTuple);
break;
}
}
headerFile.append(generatorInstance.getHeaderFileContents());
implementationFile.append(generatorInstance.getWrapperFileContents());
nativesFile.append(generatorInstance.getNativesFileContents());
for (ClassWithOptions innerClass : innerClasses) {
generateClass(innerClass);
}
}
private static String getHeaderGuardName(final String name) {
return name.replaceAll("\\W", "_");
}

Просмотреть файл

@ -485,6 +485,21 @@ public class CodeGenerator {
}
}
public void generateClasses(final ClassWithOptions[] classes) {
if (classes.length == 0) {
return;
}
header.append(
"public:\n");
for (final ClassWithOptions cls : classes) {
// Extract "Inner" from "Outer::Inner".
header.append(
" class " + Utils.getUnqualifiedName(cls.generatedName) + ";\n");
}
header.append('\n');
}
/**
* Get the finalised bytes to go into the generated wrappers file.
*

Просмотреть файл

@ -28,20 +28,21 @@ public class JarClassIterator implements Iterator<ClassWithOptions> {
String className = mTargetClassListIterator.next();
try {
Class<?> ret = mTarget.loadClass(className);
final String canonicalName;
// Incremental builds can leave stale classfiles in the jar. Such classfiles will cause
// an exception at this point. We can safely ignore these classes - they cannot possibly
// ever be loaded as they conflict with their parent class and will be killed by Proguard
// later on anyway.
// ever be loaded as they conflict with their parent class and will be killed by
// Proguard later on anyway.
final Class<?> enclosingClass;
try {
canonicalName = ret.getCanonicalName();
enclosingClass = ret.getEnclosingClass();
} catch (IncompatibleClassChangeError e) {
return next();
}
if (canonicalName == null || "null".equals(canonicalName)) {
if (enclosingClass != null) {
// Anonymous inner class - unsupported.
// Or named inner class, which will be processed when we process the outer class.
return next();
}

Просмотреть файл

@ -6,6 +6,7 @@ package org.mozilla.gecko.annotationProcessors.utils;
import org.mozilla.gecko.annotationProcessors.AnnotationInfo;
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
@ -13,6 +14,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
/**
@ -21,13 +23,17 @@ import java.util.Iterator;
* parameters) and the argument.
*/
public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
private final ClassWithOptions mClass;
private final Member[] mObjects;
private AnnotatableEntity mNextReturnValue;
private int mElementIndex;
private boolean mIterateEveryEntry;
public GeneratableElementIterator(Class<?> aClass) {
public GeneratableElementIterator(ClassWithOptions annotatedClass) {
mClass = annotatedClass;
final Class<?> aClass = annotatedClass.wrappedClass;
// Get all the elements of this class as AccessibleObjects.
Member[] aMethods = aClass.getDeclaredMethods();
Member[] aFields = aClass.getDeclaredFields();
@ -59,6 +65,56 @@ public class GeneratableElementIterator implements Iterator<AnnotatableEntity> {
findNextValue();
}
private Class<?>[] getFilteredInnerClasses() {
// Go through all inner classes and see which ones we want to generate.
final Class<?>[] candidates = mClass.wrappedClass.getDeclaredClasses();
int count = 0;
for (int i = 0; i < candidates.length; ++i) {
final GeneratableElementIterator testIterator
= new GeneratableElementIterator(new ClassWithOptions(candidates[i], null));
if (testIterator.hasNext()
|| testIterator.getFilteredInnerClasses() != null) {
count++;
continue;
}
// Clear out ones that don't match.
candidates[i] = null;
}
return count > 0 ? candidates : null;
}
public ClassWithOptions[] getInnerClasses() {
final Class<?>[] candidates = getFilteredInnerClasses();
if (candidates == null) {
return new ClassWithOptions[0];
}
int count = 0;
for (Class<?> candidate : candidates) {
if (candidate != null) {
count++;
}
}
final ClassWithOptions[] ret = new ClassWithOptions[count];
count = 0;
for (Class<?> candidate : candidates) {
if (candidate != null) {
ret[count++] = new ClassWithOptions(
candidate, mClass.generatedName + "::" + candidate.getSimpleName());
}
}
assert ret.length == count;
Arrays.sort(ret, new Comparator<ClassWithOptions>() {
@Override public int compare(ClassWithOptions lhs, ClassWithOptions rhs) {
return lhs.generatedName.compareTo(rhs.generatedName);
}
});
return ret;
}
/**
* Find and cache the next appropriately annotated method, plus the annotation parameter, if
* one exists. Otherwise cache null, so hasNext returns false.

Просмотреть файл

@ -225,6 +225,10 @@ public class Utils {
return member.getName();
}
public static String getUnqualifiedName(String name) {
return name.substring(name.lastIndexOf(':') + 1);
}
/**
* Determine if a member is declared static.
*