/* 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 java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.util.Arrays; import java.util.Iterator; 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; public class AnnotationProcessor { private static final String NATIVES_NAME = "Natives"; private static final String WRAPPERS_NAME = "Wrappers"; private static final String EXPORT_PREFIX = "mozilla/java/"; 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 Java methods and rerun the build. Manually updating this file\n" + "// will cause your build to fail.\n" + "\n"; public static void main(String[] args) { // We expect a list of jars on the commandline. If missing, whinge about it. if (args.length < 2) { System.err.println("Usage: java AnnotationProcessor outprefix jarfiles ..."); System.exit(1); } final String OUTPUT_PREFIX = args[0]; final String QUALIFIER = OUTPUT_PREFIX + "JNI"; (new File(QUALIFIER)).mkdir(); System.out.println("Processing annotations..."); // We want to produce the same output as last time as often as possible. Ordering of // generated statements, therefore, needs to be consistent. final String[] jars = Arrays.copyOfRange(args, 1, args.length); Arrays.sort(jars); // Start the clock! long s = System.currentTimeMillis(); int ret = 0; // Get an iterator over the classes in the jar files given... Iterator jarClassIterator = IterableJarLoadingURLClassLoader.getIteratorOverJars(jars); while (jarClassIterator.hasNext()) { final ClassWithOptions annotatedClass = jarClassIterator.next(); if (!annotatedClass.hasGenerated()) { continue; } final String sourceFileName = QUALIFIER + annotatedClass.generatedName + WRAPPERS_NAME + ".cpp"; final String headerFileName = QUALIFIER + File.separator + annotatedClass.generatedName + WRAPPERS_NAME + ".h"; final String headerExportedFileName = EXPORT_PREFIX + annotatedClass.generatedName + WRAPPERS_NAME + ".h"; final String nativesFileName = QUALIFIER + File.separator + annotatedClass.generatedName + NATIVES_NAME + ".h"; final String nativesExportedFileName = EXPORT_PREFIX + annotatedClass.generatedName + NATIVES_NAME + ".h"; final StringBuilder headerFile = new StringBuilder(GENERATED_COMMENT); final StringBuilder implementationFile = new StringBuilder(GENERATED_COMMENT); final StringBuilder nativesFile = new StringBuilder(GENERATED_COMMENT); headerFile.append( "#ifndef " + getHeaderGuardName(headerExportedFileName) + "\n" + "#define " + getHeaderGuardName(headerExportedFileName) + "\n" + "\n" + "#ifndef MOZ_PREPROCESSOR\n" + "#include \"mozilla/jni/Refs.h\"\n" + "#endif\n" + "\n" + "namespace mozilla {\n" + "namespace java {\n" + "\n"); implementationFile.append( "#ifndef MOZ_PREPROCESSOR\n" + "#include \"" + headerExportedFileName + "\"\n" + "#include \"mozilla/jni/Accessors.h\"\n" + "#endif\n" + "\n" + "namespace mozilla {\n" + "namespace java {\n" + "\n"); nativesFile.append( "#ifndef " + getHeaderGuardName(nativesExportedFileName) + "\n" + "#define " + getHeaderGuardName(nativesExportedFileName) + "\n" + "\n" + "#ifndef MOZ_PREPROCESSOR\n" + "#include \"" + headerExportedFileName + "\"\n" + "#include \"mozilla/jni/Natives.h\"\n" + "#endif\n" + "\n" + "namespace mozilla {\n" + "namespace java {\n" + "\n"); generateClass(annotatedClass, headerFile, implementationFile, nativesFile); implementationFile.append("} /* java */\n" + "} /* mozilla */\n"); headerFile.append( "} /* java */\n" + "} /* mozilla */\n\n" + "#endif // " + getHeaderGuardName(headerExportedFileName) + "\n"); nativesFile.append( "} /* java */\n" + "} /* mozilla */\n\n" + "#endif // " + getHeaderGuardName(nativesExportedFileName) + "\n"); ret |= writeOutputFile(sourceFileName, implementationFile); ret |= writeOutputFile(headerFileName, headerFile); ret |= writeOutputFile(nativesFileName, nativesFile); } long e = System.currentTimeMillis(); System.out.println("Annotation processing complete in " + (e - s) + "ms"); System.exit(ret); } private static void generateClass( final ClassWithOptions annotatedClass, final StringBuilder headerFile, final StringBuilder implementationFile, final StringBuilder nativesFile) { // Get an iterator over the appropriately generated methods of this class final GeneratableElementIterator methodIterator = new GeneratableElementIterator(annotatedClass); final ClassWithOptions[] innerClasses = methodIterator.getInnerClasses(); final CodeGenerator generatorInstance = new CodeGenerator(annotatedClass); generatorInstance.generateClasses(innerClasses); // Iterate all annotated members in this class.. while (methodIterator.hasNext()) { AnnotatableEntity aElementTuple = methodIterator.next(); switch (aElementTuple.mEntityType) { case METHOD: generatorInstance.generateMethod(aElementTuple); break; case NATIVE: generatorInstance.generateNative(aElementTuple); break; case FIELD: generatorInstance.generateField(aElementTuple); break; case CONSTRUCTOR: generatorInstance.generateConstructor(aElementTuple); break; } } headerFile.append(generatorInstance.getHeaderFileContents()); implementationFile.append(generatorInstance.getWrapperFileContents()); nativesFile.append(generatorInstance.getNativesFileContents()); for (ClassWithOptions innerClass : innerClasses) { generateClass(innerClass, headerFile, implementationFile, nativesFile); } } private static String getHeaderGuardName(final String name) { return name.replaceAll("\\W", "_"); } private static int writeOutputFile(final String name, final StringBuilder content) { final byte[] contentBytes = content.toString().getBytes(StandardCharsets.UTF_8); try { final byte[] existingBytes = Files.readAllBytes(new File(name).toPath()); if (Arrays.equals(contentBytes, existingBytes)) { return 0; } } catch (FileNotFoundException e) { // Pass. } catch (NoSuchFileException e) { // Pass. } catch (IOException e) { System.err.println("Unable to read " + name + ". Perhaps a permissions issue?"); e.printStackTrace(System.err); return 1; } try (FileOutputStream outStream = new FileOutputStream(name)) { outStream.write(contentBytes); } catch (IOException e) { System.err.println("Unable to write " + name + ". Perhaps a permissions issue?"); e.printStackTrace(System.err); return 1; } return 0; } }