diff options
Diffstat (limited to 'build/annotationProcessors/SDKProcessor.java')
-rw-r--r-- | build/annotationProcessors/SDKProcessor.java | 258 |
1 files changed, 258 insertions, 0 deletions
diff --git a/build/annotationProcessors/SDKProcessor.java b/build/annotationProcessors/SDKProcessor.java new file mode 100644 index 000000000..7928978c0 --- /dev/null +++ b/build/annotationProcessors/SDKProcessor.java @@ -0,0 +1,258 @@ +/* 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 java {\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 java {\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" + + "} /* java */\n" + + "} /* mozilla */\n"); + + headerFile.append( + "} /* sdk */\n" + + "} /* java */\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; + } + + // Sometimes (e.g. KeyEvent) a field can appear in both a class and a superclass. In + // that case we want to filter out the version that appears in the superclass, or + // we'll have bindings with duplicate names. + try { + if (m instanceof Field && !m.equals(cls.getField(m.getName()))) { + // m is a field in a superclass that has been hidden by + // a field with the same name in a subclass. + System.out.println("Skipping " + m.getName() + + " from " + m.getDeclaringClass()); + continue; + } + } catch (final NoSuchFieldException e) { + } + + 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); + } + } + } + } +} |