/* * Copyright (c) 2002-2008 LWJGL Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'LWJGL' nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.lwjgl.util.generator; import java.io.*; import java.lang.annotation.Annotation; import java.nio.*; import java.nio.channels.FileChannel; import java.util.*; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementKindVisitor7; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; import javax.tools.StandardLocation; /** * * Generator visitor for the generator tool * * @author elias_naur * @version $Revision$ $Id$ */ public class GeneratorVisitor extends ElementKindVisitor7 { private final ProcessingEnvironment env; private final TypeMap type_map; private final boolean generate_error_checks; private final boolean context_specific; private final long generatorLM; public GeneratorVisitor(ProcessingEnvironment env, TypeMap type_map, boolean generate_error_checks, boolean context_specific, long generatorLM) { this.env = env; this.type_map = type_map; this.generate_error_checks = generate_error_checks; this.context_specific = context_specific; this.generatorLM = generatorLM; } private void validateMethod(ExecutableElement method) { if (method.isVarArgs()) { throw new RuntimeException("Method " + method.getSimpleName() + " is variadic"); } Collection modifiers = method.getModifiers(); if (!modifiers.contains(Modifier.PUBLIC)) { throw new RuntimeException("Method " + method.getSimpleName() + " is not public"); } if (method.getThrownTypes().size() > 0) { throw new RuntimeException("Method " + method.getSimpleName() + " throws checked exceptions"); } validateParameters(method); StripPostfix strip_annotation = method.getAnnotation(StripPostfix.class); if (strip_annotation != null && method.getAnnotation(Alternate.class) == null) { String postfix_param_name = strip_annotation.value(); VariableElement postfix_param = Utils.findParameter(method, postfix_param_name); if (Utils.isParameterMultiTyped(postfix_param)) { throw new RuntimeException("Postfix parameter can't be the same as a multityped parameter in method " + method); } if (Utils.getNIOBufferType(postfix_param.asType()) == null) { throw new RuntimeException("Postfix parameter type must be a nio Buffer"); } } if (Utils.getResultParameter(method) != null && !method.getReturnType().equals(env.getTypeUtils().getNoType(TypeKind.VOID))) { throw new RuntimeException(method + " return type is not void but a parameter is annotated with Result"); } if (method.getAnnotation(CachedResult.class) != null) { if (Utils.getNIOBufferType(Utils.getMethodReturnType(method)) == null) { throw new RuntimeException(method + " return type is not a Buffer, but is annotated with CachedResult"); } if (method.getAnnotation(AutoSize.class) == null) { throw new RuntimeException(method + " is annotated with CachedResult but misses an AutoSize annotation"); } } validateTypes(method, method.getAnnotationMirrors(), method.getReturnType()); } private void validateType(ExecutableElement method, Class annotation_type, Class type) { Class[] valid_types = type_map.getValidAnnotationTypes(type); for (Class valid_type : valid_types) { if (valid_type.equals(annotation_type)) { return; } } throw new RuntimeException(type + " is annotated with invalid native type " + annotation_type + " in method " + method); } private void validateTypes(ExecutableElement method, List annotations, TypeMirror type_mirror) { for (AnnotationMirror annotation : annotations) { NativeType native_type_annotation = NativeTypeTranslator.getAnnotation(annotation, NativeType.class); if (native_type_annotation != null) { Class annotation_type = NativeTypeTranslator.getClassFromType(annotation.getAnnotationType()); Class type = Utils.getJavaType(type_mirror); if (Buffer.class.equals(type)) { continue; } validateType(method, annotation_type, type); } } } private void validateParameters(ExecutableElement method) { for (VariableElement param : method.getParameters()) { validateTypes(method, param.getAnnotationMirrors(), param.asType()); Class param_type = Utils.getJavaType(param.asType()); if (Utils.getNIOBufferType(param.asType()) != null && param_type != CharSequence.class && param_type != CharSequence[].class) { Check parameter_check_annotation = param.getAnnotation(Check.class); NullTerminated null_terminated_annotation = param.getAnnotation(NullTerminated.class); if (parameter_check_annotation == null && null_terminated_annotation == null) { boolean found_auto_size_param = false; for (VariableElement inner_param : method.getParameters()) { AutoSize auto_size_annotation = inner_param.getAnnotation(AutoSize.class); if (auto_size_annotation != null && auto_size_annotation.value().equals(param.getSimpleName().toString())) { found_auto_size_param = true; break; } } if (!found_auto_size_param && param.getAnnotation(Result.class) == null && param.getAnnotation(Constant.class) == null && !Utils.isReturnParameter(method, param)) { throw new RuntimeException(param + " has no Check, Result nor Constant annotation, is not the return parameter and no other parameter has" + " an @AutoSize annotation on it in method " + method); } } if (param.getAnnotation(CachedReference.class) != null && param.getAnnotation(Result.class) != null) { throw new RuntimeException(param + " can't be annotated with both CachedReference and Result"); } if (param.getAnnotation(BufferObject.class) != null && param.getAnnotation(Result.class) != null) { throw new RuntimeException(param + " can't be annotated with both BufferObject and Result"); } //if (param.getAnnotation(Constant.class) != null) //throw new RuntimeException("Buffer parameter " + param + " cannot be Constant"); } else { if (param.getAnnotation(BufferObject.class) != null) { throw new RuntimeException(param + " type is not a buffer, but annotated as a BufferObject"); } if (param.getAnnotation(CachedReference.class) != null) { throw new RuntimeException(param + " type is not a buffer, but annotated as a CachedReference"); } } } } private static void generateMethodsNativePointers(PrintWriter writer, Collection methods) { for (ExecutableElement method : methods) { if (method.getAnnotation(Alternate.class) == null) { generateMethodNativePointers(writer, method); } } } private static void generateMethodNativePointers(PrintWriter writer, ExecutableElement method) { if (method.getAnnotation(Extern.class) == null) { writer.print("static "); } writer.println(Utils.getTypedefName(method) + " " + method.getSimpleName() + ";"); } private void generateJavaSource(TypeElement d, PrintWriter java_writer) throws IOException { java_writer.println("/* MACHINE GENERATED FILE, DO NOT EDIT */"); java_writer.println(); java_writer.println("package " + env.getElementUtils().getPackageOf(d).getQualifiedName().toString() + ";"); java_writer.println(); java_writer.println("import org.lwjgl.*;"); java_writer.println("import java.nio.*;"); Imports imports = d.getAnnotation(Imports.class); if (imports != null) { for (String i : imports.value()) { java_writer.println("import " + i + ";"); } } java_writer.println(); Utils.printDocComment(java_writer, d, env); if (d.getAnnotation(Private.class) == null) { java_writer.print("public "); } boolean is_final = Utils.isFinal(d); if (is_final) { java_writer.write("final "); } java_writer.print("class " + Utils.getSimpleClassName(d)); List super_interfaces = d.getInterfaces(); if (super_interfaces.size() > 1) { throw new RuntimeException(d + " extends more than one interface"); } if (super_interfaces.size() == 1) { TypeMirror super_interface = super_interfaces.iterator().next(); java_writer.print(" extends " + Utils.getSimpleClassName(env.getElementUtils().getTypeElement(super_interface.toString()))); } java_writer.println(" {"); FieldsGenerator.generateFields(env, java_writer, Utils.getFields(d)); java_writer.println(); if (is_final) { // Write private constructor to avoid instantiation java_writer.println("\tprivate " + Utils.getSimpleClassName(d) + "() {}"); } if (Utils.getMethods(d).size() > 0 && !context_specific) { java_writer.println(); java_writer.println("\tstatic native void " + Utils.STUB_INITIALIZER_NAME + "() throws LWJGLException;"); } JavaMethodsGenerator.generateMethodsJava(env, type_map, java_writer, d, generate_error_checks, context_specific); java_writer.println("}"); java_writer.close(); String qualified_interface_name = Utils.getQualifiedClassName(d); env.getMessager().printMessage(Diagnostic.Kind.NOTE, "Generated class " + qualified_interface_name); } private void generateNativeSource(TypeElement d) throws IOException { if (d.getKind().equals(ElementKind.ANNOTATION_TYPE)) { return; } String qualified_interface_name = Utils.getQualifiedClassName(d); String qualified_native_name = Utils.getNativeQualifiedName(qualified_interface_name) + ".c"; PrintWriter native_writer = new PrintWriter(env.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", qualified_native_name).openWriter()); native_writer.println("/* MACHINE GENERATED FILE, DO NOT EDIT */"); native_writer.println(); native_writer.println("#include "); type_map.printNativeIncludes(native_writer); native_writer.println(); TypedefsGenerator.generateNativeTypedefs(type_map, native_writer, Utils.getMethods(d)); native_writer.println(); if (!context_specific) { generateMethodsNativePointers(native_writer, Utils.getMethods(d)); native_writer.println(); } NativeMethodStubsGenerator.generateNativeMethodStubs(env, type_map, native_writer, d, generate_error_checks, context_specific); if (!context_specific) { native_writer.print("JNIEXPORT void JNICALL " + Utils.getQualifiedNativeMethodName(qualified_interface_name, Utils.STUB_INITIALIZER_NAME)); native_writer.println("(JNIEnv *env, jclass clazz) {"); native_writer.println("\tJavaMethodAndExtFunction functions[] = {"); RegisterStubsGenerator.generateMethodsNativeStubBind(env, native_writer, d, generate_error_checks, context_specific); native_writer.println("\t};"); native_writer.println("\tint num_functions = NUMFUNCTIONS(functions);"); native_writer.print("\t"); native_writer.print(type_map.getRegisterNativesFunctionName()); native_writer.println("(env, clazz, num_functions, functions);"); native_writer.println("}"); } native_writer.close(); env.getMessager().printMessage(Kind.NOTE, "Generated C source " + qualified_interface_name); } @Override public Void visitTypeAsInterface(TypeElement e, Void p) { PrintWriter java_writer = null; try { final Collection methods = Utils.getMethods(e); if (methods.isEmpty() && Utils.getFields(e).isEmpty()) { return DEFAULT_VALUE; } env.getMessager().printMessage(Kind.NOTE, "methods count : " + Utils.getMethods(e).size() + " fields count : " + Utils.getFields(e).size(), e); for (final ExecutableElement method : methods) { validateMethod(method); } java_writer = new PrintWriter(env.getFiler().createSourceFile(Utils.getQualifiedClassName(e), env.getElementUtils().getPackageOf(e)).openWriter()); generateJavaSource(e, java_writer); if (methods.size() > 0) { boolean noNative = true; for (final ExecutableElement method : methods) { Alternate alt_annotation = method.getAnnotation(Alternate.class); if ((alt_annotation == null || alt_annotation.nativeAlt()) && method.getAnnotation(Reuse.class) == null) { noNative = false; break; } } if (noNative) { return DEFAULT_VALUE; } boolean outputNativeExists = true, outputBackupExists = true; File outputNative = null, outputBackup = null; try { final FileObject fo_outputNative = env.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", Utils.getNativeQualifiedName(Utils.getQualifiedClassName(e)).replace(".", "/") + ".c"); outputNative = new File(fo_outputNative.toUri()); outputNativeExists = outputNative.exists(); final FileObject fo_outputBackup = env.getFiler().getResource(StandardLocation.CLASS_OUTPUT, "", Utils.getNativeQualifiedName(Utils.getQualifiedClassName(e)).replace(".", "/") + "_backup.c"); outputBackup = new File(fo_outputBackup.toUri()); outputBackupExists = outputBackup.exists(); } catch (IOException ex) { if (outputNative == null) { outputNativeExists = false; } if (outputBackup == null) { outputBackupExists = false; } } finally { // If the native file exists, rename. final ByteBuffer nativeBefore; if (outputNativeExists) { nativeBefore = readFile(outputNative); if (outputBackupExists) { outputBackup.delete(); } outputNative.renameTo(outputBackup); } else { nativeBefore = null; } try { generateNativeSource(e); // If the native file did exist, compare with the new file. If they're the same, // reset the last modified time to avoid ridiculous C compilation times. if (nativeBefore != null && outputNative.length() == nativeBefore.capacity()) { final ByteBuffer nativeAfter = readFile(outputNative); boolean same = true; for (int i = nativeBefore.position(); i < nativeBefore.limit(); i++) { if (nativeBefore.get(i) != nativeAfter.get(i)) { same = false; break; } } if (same) { outputNative.delete(); outputBackup.renameTo(outputNative); } } } catch (IOException ex) { throw new RuntimeException(ex); } finally { if (outputBackup.exists()) { outputBackup.delete(); } } } } return DEFAULT_VALUE; } catch (Exception ex) { // If anything goes wrong mid-gen, delete output to allow regen next time we run. if (java_writer != null) { java_writer.close(); } throw new RuntimeException(ex); } } private static ByteBuffer readFile(final File file) throws IOException { final FileChannel channel = new FileInputStream(file).getChannel(); final long bytesTotal = channel.size(); final ByteBuffer buffer = ByteBuffer.allocateDirect((int) bytesTotal); long bytesRead = 0; do { bytesRead += channel.read(buffer); } while (bytesRead < bytesTotal); buffer.flip(); channel.close(); return buffer; } }