/* 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 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 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() { @Override public int compare(Member a, Member b) { return a.getName().compareTo(b.getName()); } }); ArrayList 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 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); } } } } }