| /* 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; |
| } |
| |
| // 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); |
| } |
| } |
| } |
| } |
| } |