/* 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.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; 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; /** * Iterator over the methods in a given method list which have the WrappedJNIMethod * annotation. Returns an object containing both the annotation (Which may contain interesting * parameters) and the argument. */ public class GeneratableElementIterator implements Iterator { private final ClassWithOptions mClass; private final Member[] mObjects; private AnnotatableEntity mNextReturnValue; private int mElementIndex; private boolean mIterateEveryEntry; 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(); Member[] aCtors = aClass.getDeclaredConstructors(); // Shove them all into one buffer. Member[] objs = new Member[aMethods.length + aFields.length + aCtors.length]; int offset = 0; System.arraycopy(aMethods, 0, objs, 0, aMethods.length); offset += aMethods.length; System.arraycopy(aFields, 0, objs, offset, aFields.length); offset += aFields.length; System.arraycopy(aCtors, 0, objs, offset, aCtors.length); // Sort the elements to ensure determinism. Arrays.sort(objs, new AlphabeticAnnotatableEntityComparator()); mObjects = objs; // Check for "Wrap ALL the things" flag. for (Annotation annotation : aClass.getDeclaredAnnotations()) { final String annotationTypeName = annotation.annotationType().getName(); if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) { mIterateEveryEntry = true; break; } } 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() { @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. */ private void findNextValue() { while (mElementIndex < mObjects.length) { Member candidateElement = mObjects[mElementIndex]; mElementIndex++; for (Annotation annotation : ((AnnotatedElement) candidateElement).getDeclaredAnnotations()) { // WrappedJNIMethod has parameters. Use Reflection to obtain them. Class annotationType = annotation.annotationType(); final String annotationTypeName = annotationType.getName(); if (annotationTypeName.equals("org.mozilla.gecko.annotation.WrapForJNI")) { String stubName = null; boolean isMultithreadedStub = false; boolean noThrow = false; boolean narrowChars = false; boolean catchException = false; try { // Determine the explicitly-given name of the stub to generate, if any. final Method stubNameMethod = annotationType.getDeclaredMethod("stubName"); stubNameMethod.setAccessible(true); stubName = (String) stubNameMethod.invoke(annotation); // Determine if the generated stub is to allow calls from multiple threads. final Method multithreadedStubMethod = annotationType.getDeclaredMethod("allowMultithread"); multithreadedStubMethod.setAccessible(true); isMultithreadedStub = (Boolean) multithreadedStubMethod.invoke(annotation); // Determine if ignoring exceptions final Method noThrowMethod = annotationType.getDeclaredMethod("noThrow"); noThrowMethod.setAccessible(true); noThrow = (Boolean) noThrowMethod.invoke(annotation); // Determine if strings should be wide or narrow final Method narrowCharsMethod = annotationType.getDeclaredMethod("narrowChars"); narrowCharsMethod.setAccessible(true); narrowChars = (Boolean) narrowCharsMethod.invoke(annotation); // Determine if we should catch exceptions final Method catchExceptionMethod = annotationType.getDeclaredMethod("catchException"); catchExceptionMethod.setAccessible(true); catchException = (Boolean) catchExceptionMethod.invoke(annotation); } catch (NoSuchMethodException e) { System.err.println("Unable to find expected field on WrapForJNI annotation. Did the signature change?"); e.printStackTrace(System.err); System.exit(3); } catch (IllegalAccessException e) { System.err.println("IllegalAccessException reading fields on WrapForJNI annotation. Seems the semantics of Reflection have changed..."); e.printStackTrace(System.err); System.exit(4); } catch (InvocationTargetException e) { System.err.println("InvocationTargetException reading fields on WrapForJNI annotation. This really shouldn't happen."); e.printStackTrace(System.err); System.exit(5); } // If the method name was not explicitly given in the annotation generate one... if (stubName.isEmpty()) { stubName = Utils.getNativeName(candidateElement); } AnnotationInfo annotationInfo = new AnnotationInfo( stubName, isMultithreadedStub, noThrow, narrowChars, catchException); mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo); return; } } // If no annotation found, we might be expected to generate anyway // using default arguments, thanks to the "Generate everything" annotation. if (mIterateEveryEntry) { AnnotationInfo annotationInfo = new AnnotationInfo( Utils.getNativeName(candidateElement), /* multithreaded */ true, /* noThrow */ false, /* narrowChars */ false, /* catchException */ false); mNextReturnValue = new AnnotatableEntity(candidateElement, annotationInfo); return; } } mNextReturnValue = null; } @Override public boolean hasNext() { return mNextReturnValue != null; } @Override public AnnotatableEntity next() { AnnotatableEntity ret = mNextReturnValue; findNextValue(); return ret; } @Override public void remove() { throw new UnsupportedOperationException("Removal of methods from GeneratableElementIterator not supported."); } }