lwjgl/src/java/org/lwjgl/util/generator/GeneratorVisitor.java

396 lines
24 KiB
Java
Raw Normal View History

/*
2008-04-07 14:36:09 -04:00
* 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.
*/
2005-02-20 14:28:34 -05:00
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 <elias_naur@users.sourceforge.net>
* @version $Revision$ $Id$
*/
public class GeneratorVisitor extends ElementKindVisitor7<Void, Void> {
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<Modifier> 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<? extends Annotation> 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<? extends AnnotationMirror> annotations, TypeMirror type_mirror) {
for (AnnotationMirror annotation : annotations) {
NativeType native_type_annotation = NativeTypeTranslator.getAnnotation(annotation, NativeType.class);
if (native_type_annotation != null) {
Class<? extends Annotation> 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<? extends ExecutableElement> 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<? extends TypeMirror> 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 <jni.h>");
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<? extends ExecutableElement> 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;
}
}