The API version detection functionality was broken in SDKProcessor because we were passing in "Lpackage/Class;" as the class name rather than just "package/Class". Also, some classes have a weird situation where some methods were moved around in later API versions. For example, some put* and get* methods in Bundle were moved to BaseBundle in API 21. If we only checked BaseBundle.put*, we would think they are API 21+ only. The workaround is to check both the top-level class and the declaring class for a member, and choose the lower API level as the minimal API level for that member. This patch also fixes bugs in including the right class members. For SDKProcessor we want to include all public members of a class, including inherited members, because the private/protected members are not part of the public API. For AnnotationProcessor, we want to include all the members declared in that class, including private and protected members, because we may want to access private/protected members of our own Java classes from C++.
245 lines
9.5 KiB
Java
245 lines
9.5 KiB
Java
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.gecko.annotationProcessors;
|
|
|
|
import com.android.tools.lint.checks.ApiLookup;
|
|
import com.android.tools.lint.LintCliClient;
|
|
|
|
import org.mozilla.gecko.annotationProcessors.classloader.AnnotatableEntity;
|
|
import org.mozilla.gecko.annotationProcessors.classloader.ClassWithOptions;
|
|
import org.mozilla.gecko.annotationProcessors.classloader.IterableJarLoadingURLClassLoader;
|
|
import org.mozilla.gecko.annotationProcessors.utils.GeneratableElementIterator;
|
|
import org.mozilla.gecko.annotationProcessors.utils.Utils;
|
|
|
|
import java.io.File;
|
|
import java.io.FileInputStream;
|
|
import java.io.FileOutputStream;
|
|
import java.io.IOException;
|
|
import java.util.Arrays;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.Iterator;
|
|
import java.util.Properties;
|
|
import java.util.Scanner;
|
|
import java.util.Vector;
|
|
import java.net.URL;
|
|
import java.net.URLClassLoader;
|
|
|
|
import java.lang.reflect.Constructor;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Member;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Modifier;
|
|
|
|
public class SDKProcessor {
|
|
public static final String GENERATED_COMMENT =
|
|
"// GENERATED CODE\n" +
|
|
"// Generated by the Java program at /build/annotationProcessors at compile time\n" +
|
|
"// from annotations on Java methods. To update, change the annotations on the\n" +
|
|
"// corresponding Javamethods and rerun the build. Manually updating this file\n" +
|
|
"// will cause your build to fail.\n" +
|
|
"\n";
|
|
|
|
private static ApiLookup sApiLookup;
|
|
private static int sMaxSdkVersion;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
// We expect a list of jars on the commandline. If missing, whinge about it.
|
|
if (args.length < 5) {
|
|
System.err.println("Usage: java SDKProcessor sdkjar classlistfile outdir fileprefix max-sdk-version");
|
|
System.exit(1);
|
|
}
|
|
|
|
System.out.println("Processing platform bindings...");
|
|
|
|
String sdkJar = args[0];
|
|
Vector classes = getClassList(args[1]);
|
|
String outdir = args[2];
|
|
String generatedFilePrefix = args[3];
|
|
sMaxSdkVersion = Integer.parseInt(args[4]);
|
|
|
|
LintCliClient lintClient = new LintCliClient();
|
|
sApiLookup = ApiLookup.get(lintClient);
|
|
|
|
// Start the clock!
|
|
long s = System.currentTimeMillis();
|
|
|
|
// 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 " + generatedFilePrefix + "_h__\n" +
|
|
"#define " + generatedFilePrefix + "_h__\n" +
|
|
"\n" +
|
|
"#include \"mozilla/jni/Refs.h\"\n" +
|
|
"\n" +
|
|
"namespace mozilla {\n" +
|
|
"namespace widget {\n" +
|
|
"namespace sdk {\n" +
|
|
"\n");
|
|
|
|
StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT);
|
|
implementationFile.append(
|
|
"#include \"" + generatedFilePrefix + ".h\"\n" +
|
|
"#include \"mozilla/jni/Accessors.h\"\n" +
|
|
"\n" +
|
|
"namespace mozilla {\n" +
|
|
"namespace widget {\n" +
|
|
"namespace sdk {\n" +
|
|
"\n");
|
|
|
|
// Used to track the calls to the various class-specific initialisation functions.
|
|
ClassLoader loader = null;
|
|
try {
|
|
loader = URLClassLoader.newInstance(new URL[] { new URL("file://" + sdkJar) },
|
|
SDKProcessor.class.getClassLoader());
|
|
} catch (Exception e) {
|
|
throw new RuntimeException(e.toString());
|
|
}
|
|
|
|
for (Iterator<String> i = classes.iterator(); i.hasNext(); ) {
|
|
String className = i.next();
|
|
System.out.println("Looking up: " + className);
|
|
|
|
generateClass(Class.forName(className, true, loader),
|
|
implementationFile,
|
|
headerFile);
|
|
}
|
|
|
|
implementationFile.append(
|
|
"} /* sdk */\n" +
|
|
"} /* widget */\n" +
|
|
"} /* mozilla */\n");
|
|
|
|
headerFile.append(
|
|
"} /* sdk */\n" +
|
|
"} /* widget */\n" +
|
|
"} /* mozilla */\n" +
|
|
"#endif\n");
|
|
|
|
writeOutputFiles(outdir, generatedFilePrefix, headerFile, implementationFile);
|
|
long e = System.currentTimeMillis();
|
|
System.out.println("SDK processing complete in " + (e - s) + "ms");
|
|
}
|
|
|
|
private static int getAPIVersion(Class<?> cls, Member m) {
|
|
if (m instanceof Method || m instanceof Constructor) {
|
|
return sApiLookup.getCallVersion(
|
|
cls.getName().replace('.', '/'),
|
|
Utils.getMemberName(m),
|
|
Utils.getSignature(m));
|
|
} else if (m instanceof Field) {
|
|
return sApiLookup.getFieldVersion(
|
|
Utils.getClassDescriptor(m.getDeclaringClass()), m.getName());
|
|
} else {
|
|
throw new IllegalArgumentException("expected member to be Method, Constructor, or Field");
|
|
}
|
|
}
|
|
|
|
private static Member[] sortAndFilterMembers(Class<?> cls, Member[] members) {
|
|
Arrays.sort(members, new Comparator<Member>() {
|
|
@Override
|
|
public int compare(Member a, Member b) {
|
|
return a.getName().compareTo(b.getName());
|
|
}
|
|
});
|
|
|
|
ArrayList<Member> list = new ArrayList<>();
|
|
for (Member m : members) {
|
|
// Sometimes (e.g. Bundle) has methods that moved to/from a superclass in a later SDK
|
|
// version, so we check for both classes and see if we can find a minimum SDK version.
|
|
int version = getAPIVersion(cls, m);
|
|
final int version2 = getAPIVersion(m.getDeclaringClass(), m);
|
|
if (version2 > 0 && version2 < version) {
|
|
version = version2;
|
|
}
|
|
if (version > sMaxSdkVersion) {
|
|
System.out.println("Skipping " + m.getDeclaringClass().getName() + "." + m.getName() +
|
|
", version " + version + " > " + sMaxSdkVersion);
|
|
continue;
|
|
}
|
|
|
|
list.add(m);
|
|
}
|
|
|
|
return list.toArray(new Member[list.size()]);
|
|
}
|
|
|
|
private static void generateClass(Class<?> clazz,
|
|
StringBuilder implementationFile,
|
|
StringBuilder headerFile) {
|
|
String generatedName = clazz.getSimpleName();
|
|
|
|
CodeGenerator generator = new CodeGenerator(new ClassWithOptions(clazz, generatedName));
|
|
|
|
generator.generateMembers(sortAndFilterMembers(clazz, clazz.getConstructors()));
|
|
generator.generateMembers(sortAndFilterMembers(clazz, clazz.getMethods()));
|
|
generator.generateMembers(sortAndFilterMembers(clazz, clazz.getFields()));
|
|
|
|
headerFile.append(generator.getHeaderFileContents());
|
|
implementationFile.append(generator.getWrapperFileContents());
|
|
}
|
|
|
|
private static Vector<String> getClassList(String path) {
|
|
Scanner scanner = null;
|
|
try {
|
|
scanner = new Scanner(new FileInputStream(path));
|
|
|
|
Vector lines = new Vector();
|
|
while (scanner.hasNextLine()) {
|
|
lines.add(scanner.nextLine());
|
|
}
|
|
return lines;
|
|
} catch (Exception e) {
|
|
System.out.println(e.toString());
|
|
return null;
|
|
} finally {
|
|
if (scanner != null) {
|
|
scanner.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void writeOutputFiles(String aOutputDir, String aPrefix, StringBuilder aHeaderFile,
|
|
StringBuilder aImplementationFile) {
|
|
FileOutputStream implStream = null;
|
|
try {
|
|
implStream = new FileOutputStream(new File(aOutputDir, aPrefix + ".cpp"));
|
|
implStream.write(aImplementationFile.toString().getBytes());
|
|
} catch (IOException e) {
|
|
System.err.println("Unable to write " + aOutputDir + ". Perhaps a permissions issue?");
|
|
e.printStackTrace(System.err);
|
|
} finally {
|
|
if (implStream != null) {
|
|
try {
|
|
implStream.close();
|
|
} catch (IOException e) {
|
|
System.err.println("Unable to close implStream due to "+e);
|
|
e.printStackTrace(System.err);
|
|
}
|
|
}
|
|
}
|
|
|
|
FileOutputStream headerStream = null;
|
|
try {
|
|
headerStream = new FileOutputStream(new File(aOutputDir, aPrefix + ".h"));
|
|
headerStream.write(aHeaderFile.toString().getBytes());
|
|
} catch (IOException e) {
|
|
System.err.println("Unable to write " + aOutputDir + ". Perhaps a permissions issue?");
|
|
e.printStackTrace(System.err);
|
|
} finally {
|
|
if (headerStream != null) {
|
|
try {
|
|
headerStream.close();
|
|
} catch (IOException e) {
|
|
System.err.println("Unable to close headerStream due to "+e);
|
|
e.printStackTrace(System.err);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|