org.moe.natj.processor.cxx.CxxReifier.java Source code

Java tutorial

Introduction

Here is the source code for org.moe.natj.processor.cxx.CxxReifier.java

Source

/*
Copyright 2014-2016 Intel Corporation
    
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
    
http://www.apache.org/licenses/LICENSE-2.0
    
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package org.moe.natj.processor.cxx;

import org.moe.natj.processor.Config;
import org.moe.natj.processor.Main;
import org.moe.natj.processor.cxx.decl.DeclInfo;
import org.moe.natj.processor.cxx.decl.ParamInfo;
import org.moe.natj.processor.cxx.decl.invokable.*;
import org.moe.natj.processor.cxx.decl.variable.VariableInfo;
import org.moe.natj.processor.cxx.decl.variable.VariableKind;
import org.moe.natj.processor.cxx.visitors.CxxAnalyzer;
import org.moe.natj.processor.cxx.visitors.CxxClassWriter;
import org.objectweb.asm.*;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;

/**
 * This class reifies related native code and bytecode for CxxClassInfo objects.
 */
public class CxxReifier implements Opcodes {

    /*
    Data from analyzer
     */

    /**
     * Class infos to reify.
     */
    private final List<CxxClassInfo> classInfos;

    /**
     * Other declarations to reify.
     */
    private final List<CxxDeclCollection> otherDeclCollections;

    /**
     * C++ analyzer.
     */
    private final CxxAnalyzer analyzer;

    /**
     * Configuration.
     */
    private final Config configuration;

    /*
    Data for generation
     */

    /**
     * C++ source/header code buffer.
     */
    private final CxxSource source = new CxxSource();

    /**
     * Create a new CxxReifier.
     *
     * @param classInfos           CxxClassInfos to reify
     * @param headers              Native header files
     * @param userHeaders          Native user header files
     * @param otherDeclCollections Other declarations to reify
     * @param analyzer             C++ analyzer
     * @param configuration        Configuration
     */
    public CxxReifier(Collection<CxxClassInfo> classInfos, Collection<String> headers,
            Collection<String> userHeaders, Collection<CxxDeclCollection> otherDeclCollections,
            CxxAnalyzer analyzer, Config configuration) {
        // Sort and store class infos
        ArrayList<CxxClassInfo> classInfosList = new ArrayList<>(classInfos);
        classInfosList.sort((a, b) -> a.cxxClassName.compareTo(b.cxxClassName));
        this.classInfos = Collections.unmodifiableList(new ArrayList<>(classInfosList));

        source.writeBoth().comment().setContents("DO NOT EDIT THIS FILE - it is machine generated").write();
        source.writeSource().includeUser("CxxRuntime.h").nl();
        source.writeHeader().include("jni.h").nl();

        // Sort and store headers
        ArrayList<String> headersList = new ArrayList<>(headers);
        Collections.sort(headersList);
        source.writeSource();
        headersList.forEach(source::include);
        if (headersList.size() > 0)
            source.nl();

        // Sort and store user headers
        ArrayList<String> userHeadersList = new ArrayList<>(userHeaders);
        Collections.sort(userHeadersList);
        source.writeSource();
        userHeadersList.forEach(source::includeUser);
        if (userHeadersList.size() > 0)
            source.nl();

        // Sort and store standalone decl collections
        ArrayList<CxxDeclCollection> standaloneCxxDeclCollectionsList = new ArrayList<>(otherDeclCollections);
        standaloneCxxDeclCollectionsList
                .sort((a, b) -> a.getImplClass().getInternalName().compareTo(b.getImplClass().getInternalName()));
        this.otherDeclCollections = Collections.unmodifiableList(standaloneCxxDeclCollectionsList);

        this.analyzer = analyzer;
        this.configuration = configuration;
    }

    /**
     * Flush the native source buffer to the target file.
     *
     * @param cxxStubPath Source file path
     * @param hxxStubPath Header file path
     * @throws IOException When an exception is encountered
     */
    public void flushNativeStubBuilder(Path cxxStubPath, Path hxxStubPath) throws IOException {
        // Write to file
        Main.prepareOutputDirectory(cxxStubPath);
        Files.write(cxxStubPath, source.getSource().getBytes("UTF-8"));
        if (hxxStubPath != null) {
            Main.prepareOutputDirectory(hxxStubPath);
            Files.write(hxxStubPath, source.getHeader().getBytes("UTF-8"));
        }
    }

    /**
     * Reify the class infos.
     *
     * @param targetVersion Target bytecode version map
     * @return List of reified class bytecodes
     */
    public List<byte[]> reify(HashMap<String, Integer> targetVersion) {
        ArrayList<byte[]> bytes = new ArrayList<>();

        // Create inherited class bodies
        classInfos.forEach(this::reifyNativeClass);

        source.writeBoth().w("#ifdef __cplusplus").nl().w("extern \"C\" {").nl().w("#endif /* __cplusplus */").nl()
                .nl();

        // Created binding classes and methods
        classInfos.forEach(classInfo -> {
            if (classInfo.isInherited) {
                reifyInherited(bytes, targetVersion, classInfo);
            } else {
                reifyBinding(bytes, targetVersion, classInfo);
            }
        });

        // Create other code
        otherDeclCollections.forEach(collection -> reify(bytes, targetVersion, collection));

        // Create type resolvers

        // Pragma mark
        source.writeBoth();
        source.pragmaMark("- Type Resolvers").nl();
        CxxType.getTypeResolverPlugins().forEach(plugin -> reifyTypeResolverPlugin(bytes, targetVersion, plugin));

        source.writeBoth().w("#ifdef __cplusplus").nl().w("} /* extern \"C\" */").nl().w("#endif /* __cplusplus */")
                .nl().nl();

        return bytes;
    }

    /**
     * Reify a type resolver plugin.
     *
     * @param bytes         List to add the reified bytecode to
     * @param targetVersion Target bytecode version map
     * @param plugin        Type resolver plugin
     */
    private void reifyTypeResolverPlugin(ArrayList<byte[]> bytes, HashMap<String, Integer> targetVersion,
            ITypeResolverPlugin plugin) {
        Integer version = targetVersion.get(plugin.getImplementingClass());
        if (version == null) {
            version = 50; // Default to Java 6
        }

        final ClassWriter cw = new CxxClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        final Type superCls = Type.getObjectType("java/lang/Object");
        final String clazzName = plugin.getImplementingClass() + "$__cxx_TypeRes";
        cw.visit(version, ACC_PUBLIC + ACC_SUPER, clazzName, null, superCls.getInternalName(), new String[] {});
        cw.visitSource(null, null);

        // Create default constructor
        BCGen.constructor(cw, superCls);

        // Create global map field
        final FieldVisitor fvc = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL, "constMap",
                "Ljava/util/HashMap;", null, null);
        fvc.visitEnd();
        final FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_STATIC + ACC_FINAL, "map", "Ljava/util/HashMap;",
                null, null);
        fv.visitEnd();

        // Generate getClassForType method
        BCGen.typeResolverClassForType(cw, clazzName, true);
        BCGen.typeResolverClassForType(cw, clazzName, false);
        BCGen.typeResolverTypeIndex(cw);
        BCGen.typeResolverTypeIndexMap(cw, clazzName, CxxType.getTypeResolverPluginConfig(plugin));

        source.writeBoth();
        source.commentBlock().setContents("" + "Type resolver interface: " + plugin.getImplementingClass()).write();
        final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
        function.setName(CxxUtils.jnify(Type.getObjectType(clazzName), "getTypeIndex"));
        function.setType(CxxType.fromType(Type.INT_TYPE));
        function.addArg(CxxType.fromType(Type.LONG_TYPE));
        function.setFirstArgAsPeer(false);
        function.write();
        source.writeHeader().w(";").nl();
        source.writeSource().block()
                .write(src -> src.w(plugin.nativeTypeIDResolver(function.getParamNames().get(0))).nl());
        source.writeBoth().nl();

        cw.visitEnd();
        bytes.add(cw.toByteArray());
    }

    /**
     * Create a new ClassWriter.
     *
     * @param classInfo     Class info to create the writer for
     * @param targetVersion Target bytecode version map
     * @param type          Class type
     * @param superType     Super class type
     * @param interfaces    Super interface types
     * @return ClassWriter instance
     */
    private ClassWriter newClassWriter(CxxClassInfo classInfo, HashMap<String, Integer> targetVersion, Type type,
            Type superType, Type... interfaces) {
        return newClassWriter(classInfo, targetVersion, type, superType, new HashSet<>(Arrays.asList(interfaces)));
    }

    /**
     * Create a new ClassWriter.
     *
     * @param classInfo     Class info to create the writer for
     * @param targetVersion Target bytecode version map
     * @param type          Class type
     * @param superType     Super class type
     * @param interfaces    Super interface types
     * @return ClassWriter instance
     */
    private ClassWriter newClassWriter(CxxClassInfo classInfo, HashMap<String, Integer> targetVersion, Type type,
            Type superType, Collection<Type> interfaces) {
        Integer version = targetVersion.get(classInfo.mapperType.getInternalName());
        if (version == null) {
            throw new NullPointerException();
        }

        final ClassWriter writer = new CxxClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        writer.visit(version, ACC_PUBLIC + ACC_SUPER, type.getInternalName(), null, superType.getInternalName(),
                interfaces.stream().map(Type::getInternalName).collect(Collectors.toList())
                        .toArray(new String[interfaces.size()]));
        writer.visitSource(classInfo.mapperSourceFile, null);
        return writer;
    }

    /**
     * Reify a binding class.
     *
     * @param bytes         List to add the reified bytecode to
     * @param targetVersion Target bytecode version map
     * @param classInfo     Class info to reify
     */
    private void reifyBinding(List<byte[]> bytes, HashMap<String, Integer> targetVersion, CxxClassInfo classInfo) {
        final CxxDeclCollection declCollection = analyzer.getDeclCollection(classInfo, null);
        final Type implClass = declCollection.getImplClass();
        final Type directClass = declCollection.getDirectClass();
        final Type constImplClass = declCollection.getConstImplClass();
        final Type constDirectClass = declCollection.getConstDirectClass();
        System.out.println("    Generating " + implClass.getClassName());

        // Compute super class
        final Type superCls = CxxSupport.CXX_BINDING_OBJECT_CLS;
        final HashSet<Type> interfaces = new HashSet<>();
        if (classInfo.getCxxSupers().size() == 0) {
            interfaces.add(CxxSupport.CXX_OBJECT_ITF);
        }
        interfaces.add(classInfo.mapperType);
        interfaces.addAll(classInfo.getCxxSupers().stream().map(x -> x.mapperType).collect(Collectors.toSet()));
        interfaces.addAll(classInfo.getJavaInterfaces().stream().collect(Collectors.toSet()));

        // Pragma mark
        final String cxxClassName = classInfo.cxxClassName;
        final String className = classInfo.mapperType.getClassName();
        source.pragmaMark("- " + className + " - " + cxxClassName).nl();

        if (configuration.genCheckCxxTypeAnnotations) {
            // Write some assertions according to annotations on the type
            {
                final boolean value = classInfo.isDefaultConstructorAvailable();
                final String msg = value
                        ? "Bad binding, please mark " + className + " with @CxxDefaultConstructorUnavailable"
                        : "Bad binding, please remove @CxxDefaultConstructorUnavailable from " + className;
                source.staticAssert("std::is_default_constructible<" + cxxClassName + ">::value == " + value, msg);
            }
            {
                final boolean value = classInfo.isCopyConstructorAvailable();
                final String msg = value
                        ? "Bad binding, please mark " + className + " with @CxxCopyConstructorUnavailable"
                        : "Bad binding, please remove @CxxCopyConstructorUnavailable from " + className;
                source.staticAssert("std::is_copy_constructible<" + cxxClassName + ">::value == " + value, msg);
            }
            {
                final boolean value = classInfo.isDestructorAvailable();
                final String msg = value
                        ? "Bad binding, please mark " + className + " with @CxxDestructorUnavailable"
                        : "Bad binding, please remove @CxxDestructorUnavailable from " + className;
                source.staticAssert("std::is_destructible<" + cxxClassName + ">::value == " + value, msg);
            }
            source.nl();
        }

        // Create class writers
        final ClassWriter implWriter = newClassWriter(classInfo, targetVersion, implClass, superCls, interfaces);
        final ClassWriter directWriter = newClassWriter(classInfo, targetVersion, directClass, implClass,
                CxxSupport.CXX_DIRECT_IMPL_ITF);
        final ClassWriter constImplWriter = newClassWriter(classInfo, targetVersion, constImplClass, implClass,
                CxxSupport.CXX_CONST_IMPL_ITF);
        final ClassWriter constDirectWriter = newClassWriter(classInfo, targetVersion, constDirectClass,
                directClass, CxxSupport.CXX_CONST_IMPL_ITF);

        // Create constructors
        BCGen.pointerConstructor(implWriter, superCls);
        BCGen.pointerConstructor(directWriter, implClass);
        BCGen.pointerConstructor(constImplWriter, implClass);
        BCGen.pointerConstructor(constDirectWriter, directClass);

        // Create destructors
        if (classInfo.isDestructorUnavailable()) {
            BCGen.unavailableDeleteOp(implWriter);
        } else {
            reifyDeleteOp(implWriter, implClass, classInfo, false);
        }
        BCGen.unavailableDeleteOp(directWriter);

        // Create sizeof support
        reifySizeofOp(implWriter, implClass, classInfo);

        // Create pointer support
        reifyPtrSupport(implWriter, implClass, classInfo);

        // Reify non-members
        for (DeclInfo declInfo : declCollection.getDeclInfos()) {
            reifyToNativeBridge(implWriter, declInfo, implClass, classInfo, false, null);
        }

        // Reify member
        for (CxxMemberImplInfo implInfo : classInfo.getImplementedMethods()) {
            final DeclInfo declInfo = implInfo.declInfo;

            // Generate impl interface binding
            reifyBridgeMethod(implWriter, declInfo, implClass, classInfo, false, false, implInfo.classInfo);
            reifyBridgeMethod(constImplWriter, declInfo, constImplClass, classInfo, false, true, null);

            // Generate direct interface binding for virtual methods
            if (declInfo instanceof InvokableInfo
                    && ((InvokableInfo) declInfo).getVirtuality() == MethodVirtuality.VIRTUAL
                    && implInfo.classInfo == classInfo) {
                reifyBridgeMethod(directWriter, declInfo, directClass, classInfo, true, false, implInfo.classInfo);
                reifyBridgeMethod(constDirectWriter, declInfo, constDirectClass, classInfo, true, true, null);
            }
        }

        // Reify inherited direct only members
        for (CxxMemberImplInfo implInfo : classInfo.getInheritedDirectOnlyMethods()) {
            BCGen.toNativeBridgeInvocationOfDirectOnlyMethod(implWriter, implInfo.declInfo);
        }

        // Reify bridge methods
        classInfo.getBridgeMethodDescs().forEach(desc -> {
            final String owner = implClass.getInternalName();
            final Type oldMethodType = Type.getMethodType(desc.node.desc);
            final Type newMethodType = Type.getMethodType(desc.invokedDesc);
            BCGen.createInterfaceBridgeMethod(owner, implWriter, desc.node, oldMethodType, newMethodType, false);
        });

        { // Create "<clinit>"
          // Create _CXX_RT_SIZEOF field
            final FieldVisitor _CXX_RT_SIZEOF = implWriter.visitField(ACC_PUBLIC | ACC_STATIC | ACC_FINAL,
                    "_CXX_RT_SIZEOF", "J", null, null);
            _CXX_RT_SIZEOF.visitEnd();

            final MethodVisitor clinit = implWriter.visitMethod(ACC_STATIC, "<clinit>", "()V", null, null);
            clinit.visitCode();

            // Initialize _CXX_RT_SIZEOF field
            clinit.visitMethodInsn(INVOKESTATIC, implClass.getInternalName(), BCGen.SIZEOF_OP_BRIDGE_NAME,
                    BCGen.SIZEOF_OP_BRIDGE_DESC, false);
            clinit.visitFieldInsn(PUTSTATIC, implClass.getInternalName(), "_CXX_RT_SIZEOF", "J");

            // End
            clinit.visitInsn(RETURN);
            clinit.visitMaxs(0, 0);
            clinit.visitEnd();
        }

        // Create classes
        implWriter.visitEnd();
        bytes.add(implWriter.toByteArray());
        directWriter.visitEnd();
        bytes.add(directWriter.toByteArray());
        constImplWriter.visitEnd();
        bytes.add(constImplWriter.toByteArray());
        constDirectWriter.visitEnd();
        bytes.add(constDirectWriter.toByteArray());
    }

    /**
     * Reify an inherited class.
     *
     * @param bytes         List to add the reified bytecode to
     * @param targetVersion Target bytecode version map
     * @param classInfo     Class info to reify
     */
    private void reifyInherited(List<byte[]> bytes, HashMap<String, Integer> targetVersion,
            CxxClassInfo classInfo) {
        final CxxDeclCollection declCollection = analyzer.getDeclCollection(classInfo, null);
        final Type implClass = declCollection.getImplClass();
        System.out.println("    Generating " + implClass.getClassName());

        // Compute super class
        final Type superCls = Type.getObjectType("java/lang/Object");

        // Pragma mark
        source.pragmaMark("- " + classInfo.mapperType.getClassName() + " - " + classInfo.cxxClassName).nl();

        // Create class writers
        final ClassWriter implWriter = newClassWriter(classInfo, targetVersion, implClass, superCls);

        // Create constructors
        BCGen.pointerConstructor(implWriter, superCls);

        // Create destructors
        if (classInfo.isDestructorUnavailable()) {
            BCGen.unavailableDeleteOp(implWriter);
        } else {
            reifyDeleteOp(implWriter, implClass, classInfo, false);
        }

        // Reify non-members
        for (DeclInfo declInfo : declCollection.getDeclInfos()) {
            reifyToNativeBridge(implWriter, declInfo, implClass, classInfo, false, null);
        }

        // Reify members
        for (CxxMemberImplInfo implInfo : classInfo.getImplementedMethods()) {
            final DeclInfo declInfo = implInfo.declInfo;
            if ((declInfo.getAccess() & ACC_NATIVE) == 0)
                continue;

            // Generate impl interface binding
            reifyInheritedMethod(implWriter, declInfo, implClass, classInfo, false, implInfo.classInfo);
        }

        // Reify inherited direct only members
        for (CxxMemberImplInfo implInfo : classInfo.getInheritedDirectOnlyMethods()) {
            BCGen.toNativeBridgeInvocationOfDirectOnlyMethod(implWriter, implInfo.declInfo);
        }

        // Create classes
        implWriter.visitEnd();
        bytes.add(implWriter.toByteArray());
    }

    /**
     * Reify the native code for the specified class info.
     *
     * @param classInfo Class info
     */
    private void reifyNativeClass(CxxClassInfo classInfo) {
        if (!classInfo.isInherited) {
            return;
        }
        System.out.println("    Generating <native class> " + classInfo.cxxClassName);

        // Pragma mark
        source.writeSource();
        source.pragmaMark("- " + classInfo.mapperType.getClassName() + " - " + classInfo.cxxClassName).nl();

        // Create inherited class
        final List<CxxMemberImplInfo> membersToImpl = classInfo.getImplementedMethods();

        // Collect methods to implement
        final ArrayList<InvokableInfo> methods = new ArrayList<>();
        for (CxxMemberImplInfo implInfo : membersToImpl) {
            if (implInfo.classInfo != classInfo)
                continue;
            if (!CxxUtils.isInheritedNativeMethod(implInfo))
                continue;
            methods.add((InvokableInfo) implInfo.declInfo);
        }

        // Write to source code only
        source.writeSource();

        // Prepare class
        final CxxSource.Clazz clazz = source.clazz().setName(classInfo.cxxClassName);
        final Set<CxxClassInfo> cxxSupers = classInfo.getCxxSupers();
        if (classInfo.getCxxSupers().size() > 1) {
            source.w("#error Class ").w(classInfo.cxxClassName)
                    .w(" cannot be created, multiple supers are not " + "supported!").nl().nl();
            return;
        }
        cxxSupers.forEach(info -> clazz.addSuper(info.cxxClassName));
        clazz.setAfterClass(src -> {
            // After the class' block, create the method table
            src.writeSource();
            src.w("jmethodID ").w(src.topClass().getSimpleName()).w("::__java_method_table[] = { ");
            methods.stream().forEach(m -> src.w("0, "));
            src.w("0 };").nl();
        });

        // Write class body
        clazz.write(src -> {
            // Make everything public
            src.w("public:").nl();

            // Generate Java class name
            final String __java_class_name = classInfo.mapperType.getInternalName();
            src.w("static constexpr const char* __java_class_name = \"").w(__java_class_name).w("\";").nl();

            // Generate method table
            src.inheritedClassMethodTable().setClassName(src.topClass().getSimpleName()).setMethods(methods)
                    .write();

            // Generate forwarding constructor
            src.inheritedClassForwardingConstructor().write();

            // Delete implicit copy/move constructors and assignment operators
            src.disabledConstructors().write();

            // Generate methods
            methods.forEach(method -> {
                source.writeSource();
                final CxxSource.Function function = src.function();
                function.setConst(method.isConst());
                switch (method.getVirtuality()) {
                case INAPPLICABLE:
                    throw new IllegalStateException();
                case NON_VIRTUAL:
                    function.setVirtual(false);
                    break;
                case VIRTUAL:
                    function.setVirtual(true);
                    break;
                case PURE_VIRTUAL:
                    throw new RuntimeException("Pure virtual methods are not supported in inherited classes");
                }

                // Set name
                final InvokableKind kind = method.getKind();
                if (kind == InvokableKind.OPERATOR) {
                    if (!(method instanceof OperatorInfo)) {
                        throw new IllegalStateException();
                    }
                    OperatorInfo invInfo = (OperatorInfo) method;
                    function.setName("operator" + invInfo.getOperatorKind().getName());
                    if (invInfo.getOperatorKind() == OperatorKind.POST_INCREMENT
                            || invInfo.getOperatorKind() == OperatorKind.POST_DECREMENT) {
                        function.setPostIncOrDecOperator(true);

                    } else if (invInfo.getOperatorKind().isExtAssignable()) {
                        throw new RuntimeException("Overriding " + OperatorKind.EXT_SUBSCRIPT_ASSIGN.name()
                                + " operator in inherited classes is not supported");

                    }

                } else if (kind == InvokableKind.CAST_OPERATOR) {
                    function.setName("operator " + method.getType().getNativeType());
                    function.setOmitReturnType(true);

                } else if (kind == InvokableKind.INSTANCE_METHOD) {
                    function.setName(method.getNativeIdentifier());

                } else {
                    throw new IllegalStateException();
                }

                // Set types
                function.setType(method.getType());
                method.getParams().forEach(param -> function.addArg(param.getType()));
                function.write();
                src.block().write(src1 -> {
                    // Cast parameters on demand
                    final List<String> paramNames = function.getParamNames();
                    int paramIndex = 0;
                    for (ParamInfo paramInfo : method.getParams()) {
                        CxxType type = paramInfo.getType();
                        String castMethod = type.getJNIToNativeCast();
                        if (castMethod != null) {
                            String name = paramNames.get(paramIndex);
                            src1.w("auto casted_").w(name).w(" = ").w(castMethod).w("<").w(type.getNativeJNIType())
                                    .w(">(");
                            if (type.isByValueCxxObjectKind() || type.isByReferenceCxxObjectKind()) {
                                src1.w("&");
                            }
                            src1.w(name);
                            src1.w(");").nl();
                            paramNames.set(paramIndex, "casted_" + name);
                        }
                        ++paramIndex;
                    }

                    // Prepare place for return value
                    if (!method.getType().isVoid()) {
                        src1.w("auto result = ");
                    }

                    // Invoke C++ method
                    src1.w("::natj::").w(method.getType().getNatJInvokeFn()).w("(this, ")
                            .w(method.getJavaStaticBridgeStubMethodName());
                    for (String paramName : paramNames) {
                        src1.w(", ").w(paramName);
                    }
                    src1.w(");").nl();

                    // Return result (cast on demand)
                    if (!method.getType().isVoid()) {
                        src1.w("return ");
                        if (method.getType().isByValueCxxObjectKind()
                                || method.getType().isByReferenceCxxObjectKind()) {
                            src1.w("*(");
                        }
                        final String castMethod = method.getType().getJNIToNativeCast();
                        if (castMethod != null) {
                            src1.w(castMethod).w("<").w(method.getType().getJNICompatibleNativeType()).w(">(");
                        }
                        src1.w("result");
                        if (castMethod != null) {
                            src1.w(")");
                        }
                        if (method.getType().isByValueCxxObjectKind()
                                || method.getType().isByReferenceCxxObjectKind()) {
                            src1.w(")");
                        }
                        src1.w(";").nl();
                    }
                });
            });
        }).nl();
    }

    /**
     * Reify the delete operator for the specified class info.
     *
     * @param writer           Class writer
     * @param implClass        Implementing class type
     * @param classInfo        Class info
     * @param skipMemberMethod Don't reify the member method, only the bridge method
     */
    private void reifyDeleteOp(ClassWriter writer, Type implClass, CxxClassInfo classInfo,
            boolean skipMemberMethod) {
        assert !classInfo.isDestructorUnavailable();

        source.writeBoth();

        // Info
        final Type javaItf = classInfo.mapperType;

        if (!skipMemberMethod) {
            BCGen.deleteOp(writer, implClass);
        }
        BCGen.deleteOpBridge(writer);

        source.commentBlock()
                .setContents("" + " Interface: " + javaItf.getInternalName() + "\n" + "Impl class: "
                        + implClass.getInternalName() + "\n" + "    Method: " + BCGen.DELETE_OP_BRIDGE_NAME
                        + BCGen.DELETE_OP_BRIDGE_DESC)
                .write();
        final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
        function.setName(CxxUtils.jnify(implClass, BCGen.DELETE_OP_BRIDGE_NAME));
        function.setType(CxxType.fromType(Type.VOID_TYPE));
        function.addArg(CxxType.fromType(Type.LONG_TYPE));
        function.setFirstArgAsPeer(true);
        function.write();
        source.writeHeader().w(";").nl();
        source.writeSource().block().write(src -> {
            src.w("auto obj = reinterpret_cast<").w(classInfo.cxxClassName).w("*>(peer);").nl();
            src.w("delete obj;").nl();
        });
        source.writeBoth().nl();
    }

    /**
     * Reify the sizeof operator for the specified class info.
     *
     * @param writer    Class writer
     * @param implClass Implementing class type
     * @param classInfo Class info
     */
    private void reifySizeofOp(ClassWriter writer, Type implClass, CxxClassInfo classInfo) {
        source.writeBoth();

        // Info
        final Type javaItf = classInfo.mapperType;

        BCGen.sizeofOpBridge(writer);

        source.commentBlock()
                .setContents("" + " Interface: " + javaItf.getInternalName() + "\n" + "Impl class: "
                        + implClass.getInternalName() + "\n" + "    Method: " + BCGen.SIZEOF_OP_BRIDGE_NAME
                        + BCGen.SIZEOF_OP_BRIDGE_DESC)
                .write();
        final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
        function.setName(CxxUtils.jnify(implClass, BCGen.SIZEOF_OP_BRIDGE_NAME));
        function.setType(CxxType.fromType(Type.LONG_TYPE));
        function.write();
        source.writeHeader().w(";").nl();
        source.writeSource().block().write(src -> {
            src.w("return sizeof(").w(classInfo.cxxClassName).w(");").nl();
        });
        source.writeBoth().nl();
    }

    /**
     * Reify the pointer support for the specified class info.
     *
     * @param writer    Class writer
     * @param implClass Implementing class type
     * @param classInfo Class info
     */
    private void reifyPtrSupport(ClassWriter writer, Type implClass, CxxClassInfo classInfo) {
        source.writeSource();

        // New[] support
        if (classInfo.isDefaultConstructorAvailable() && classInfo.isDestructorAvailable()) {
            final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
            function.setName(CxxUtils.jnify(implClass, BCGen.PTR_SUPPORT_NEW_BRIDGE_NAME));
            function.setType(CxxType.fromType(Type.LONG_TYPE));
            function.addArg(CxxType.fromType(Type.INT_TYPE));
            function.write();
            source.block().write(src -> {
                final List<String> names = function.getParamNames();
                src.w("return reinterpret_cast<jlong>(new ").w(classInfo.cxxClassName).w("[").w(names.get(0))
                        .w("]());").nl();
            });
            source.nl();

            BCGen.createBridgeMethod(writer, BCGen.PTR_SUPPORT_NEW_BRIDGE_NAME, BCGen.PTR_SUPPORT_NEW_BRIDGE_DESC,
                    null, null);
        } else {
            BCGen.unavailableBridgeMethod(writer, BCGen.PTR_SUPPORT_NEW_BRIDGE_NAME,
                    BCGen.PTR_SUPPORT_NEW_BRIDGE_DESC, null, null,
                    "Default constructor support and access to the destructor is required "
                            + "to enable this feature");
        }

        // Delete[] support
        if (classInfo.isDestructorAvailable()) {
            final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
            function.setName(CxxUtils.jnify(implClass, BCGen.PTR_SUPPORT_DELETE_BRIDGE_NAME));
            function.setType(CxxType.fromType(Type.VOID_TYPE));
            function.addArg(CxxType.fromType(Type.LONG_TYPE));
            function.write();
            source.block().write(src -> {
                final List<String> names = function.getParamNames();
                src.w("delete[] reinterpret_cast<").w(classInfo.cxxClassName).w("*>(").w(names.get(0)).w(");").nl();
            });
            source.nl();

            BCGen.createBridgeMethod(writer, BCGen.PTR_SUPPORT_DELETE_BRIDGE_NAME,
                    BCGen.PTR_SUPPORT_DELETE_BRIDGE_DESC, null, null);
        } else {
            BCGen.unavailableBridgeMethod(writer, BCGen.PTR_SUPPORT_DELETE_BRIDGE_NAME,
                    BCGen.PTR_SUPPORT_DELETE_BRIDGE_DESC, null, null,
                    "Access to the destructor is required to enable this feature");
        }

        // Get[] support
        if (classInfo.isCopyConstructorAvailable()) {
            final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
            function.setName(CxxUtils.jnify(implClass, BCGen.PTR_SUPPORT_GET_BRIDGE_NAME));
            function.setType(CxxType.fromType(Type.LONG_TYPE));
            function.addArg(CxxType.fromType(Type.LONG_TYPE));
            function.addArg(CxxType.fromType(Type.INT_TYPE));
            function.write();
            source.block().write(src -> {
                final List<String> names = function.getParamNames();
                src.w("auto ptr = reinterpret_cast<").w(classInfo.cxxClassName).w("*>(").w(names.get(0)).w(");")
                        .nl();
                src.w("auto obj = new ").w(classInfo.cxxClassName).w("(ptr[").w(names.get(1)).w("]);").nl();
                src.w("return reinterpret_cast<jlong>(obj);").nl();
            });
            source.nl();

            BCGen.createBridgeMethod(writer, BCGen.PTR_SUPPORT_GET_BRIDGE_NAME, BCGen.PTR_SUPPORT_GET_BRIDGE_DESC,
                    null, null);
        } else {
            BCGen.unavailableBridgeMethod(writer, BCGen.PTR_SUPPORT_GET_BRIDGE_NAME,
                    BCGen.PTR_SUPPORT_GET_BRIDGE_DESC, null, null,
                    "Copy constructor support is required to enable this feature");
        }

        // Set[] support
        if (classInfo.isCopyConstructorAvailable()) {
            final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
            function.setName(CxxUtils.jnify(implClass, BCGen.PTR_SUPPORT_SET_BRIDGE_NAME));
            function.setType(CxxType.fromType(Type.VOID_TYPE));
            function.addArg(CxxType.fromType(Type.LONG_TYPE));
            function.addArg(CxxType.fromType(Type.INT_TYPE));
            function.addArg(CxxType.fromType(Type.LONG_TYPE));
            function.write();
            source.block().write(src -> {
                final List<String> names = function.getParamNames();
                src.w("auto ptr = reinterpret_cast<").w(classInfo.cxxClassName).w("*>(").w(names.get(0)).w(");")
                        .nl();
                src.w("auto index = static_cast<size_t>(").w(names.get(1)).w(");").nl();
                src.w("auto elem = reinterpret_cast<").w(classInfo.cxxClassName).w("*>(").w(names.get(2)).w(");")
                        .nl();
                src.w("natj::destruct_ptr_elem(&ptr[index]);").nl();
                src.w("new(&ptr[index]) ").w(classInfo.cxxClassName).w("(*elem);").nl();
            });
            source.nl();

            BCGen.createBridgeMethod(writer, BCGen.PTR_SUPPORT_SET_BRIDGE_NAME, BCGen.PTR_SUPPORT_SET_BRIDGE_DESC,
                    null, null);
        } else {
            BCGen.unavailableBridgeMethod(writer, BCGen.PTR_SUPPORT_SET_BRIDGE_NAME,
                    BCGen.PTR_SUPPORT_SET_BRIDGE_DESC, null, null,
                    "Copy constructor support is required to enable this feature");
        }
    }

    /**
     * Reify methods for a CxxDeclCollection.
     *
     * @param bytes          List to add the reified bytecode to
     * @param targetVersion  Target bytecode version map
     * @param declCollection Declaration collection to reify
     */
    private void reify(List<byte[]> bytes, HashMap<String, Integer> targetVersion,
            CxxDeclCollection declCollection) {
        final String className = declCollection.getImplClass().getClassName();
        System.out.println("    Generating " + className);

        // Pragma mark
        source.pragmaMark("- " + className).nl();

        // Visit class
        final ClassWriter implWriter = new CxxClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        Integer version = targetVersion.get(declCollection.mapperClass.getInternalName());
        if (version == null) {
            throw new NullPointerException();
        }

        implWriter.visit(version, ACC_PUBLIC + ACC_SUPER, declCollection.getImplClass().getInternalName(), null,
                "java/lang/Object", new String[0]);

        for (DeclInfo declInfo : declCollection.getDeclInfos()) {
            reifyToNativeBridge(implWriter, declInfo, declCollection.getImplClass(), null, false, null);
        }

        bytes.add(implWriter.toByteArray());
    }

    /**
     * Reify a bridge method.
     *
     * @param writer            Class writer
     * @param declInfo          Declaration info
     * @param implClass         Implementing class
     * @param classInfo         Class info
     * @param isDirectInterface Implementing class is a direct interface
     * @param isConstInterface  Implementing class is a const interface
     * @param isDirectInterface Implementing class is a direct interface
     * @param declOwner         Declaration owner
     */
    private void reifyBridgeMethod(ClassWriter writer, DeclInfo declInfo, Type implClass, CxxClassInfo classInfo,
            boolean isDirectInterface, boolean isConstInterface, CxxClassInfo declOwner) {
        System.out.println("      " + declInfo.toString());
        reifyToNativeInvoker(writer, declInfo, implClass, isConstInterface);
        if (!isConstInterface) {
            reifyToNativeBridge(writer, declInfo, implClass, classInfo, isDirectInterface, declOwner);
        }
    }

    /**
     * Reify an inherited method.
     *
     * @param writer            Class writer
     * @param declInfo          Declaration info
     * @param implClass         Implementing class
     * @param classInfo         Class info
     * @param isDirectInterface Implementing class is a direct interface
     * @param declOwner         Declaration owner
     */
    private void reifyInheritedMethod(ClassWriter writer, DeclInfo declInfo, Type implClass, CxxClassInfo classInfo,
            boolean isDirectInterface, CxxClassInfo declOwner) {
        System.out.println("      " + declInfo.toString());
        reifyToNativeBridge(writer, declInfo, implClass, classInfo, isDirectInterface, declOwner);
    }

    /**
     * Reify method non-static method which will invoke the static bridge method.
     *
     * @param writer           Class writer
     * @param declInfo         Declaration info
     * @param implClass        Implementing class
     * @param isConstInterface Implementing class is a const interface
     */
    private void reifyToNativeInvoker(ClassWriter writer, DeclInfo declInfo, Type implClass,
            boolean isConstInterface) {
        if (declInfo.getKindInfo().isClassMember()) {
            if (isConstInterface) {
                // For const interfaces throw exception on non-const methods and non-mutating variables
                if (declInfo instanceof InvokableInfo) {
                    InvokableInfo info = (InvokableInfo) declInfo;
                    if (!info.isConst()) {
                        BCGen.toNativeBridgeInvocationOfNonConstOnConst(writer, declInfo);
                    }

                } else if (declInfo instanceof VariableInfo) {
                    VariableInfo info = (VariableInfo) declInfo;
                    if (info.getKind() == VariableKind.INSTANCE_FIELD_SETTER && !info.isMutable) {
                        BCGen.toNativeBridgeInvocationOfNonConstOnConst(writer, declInfo);
                    }
                }

                // Do not generate other code!
                return;
            }

            // Generate the native bridge invoker
            BCGen.toNativeBridgeInvoker(writer, declInfo, implClass, analyzer);
        }
    }

    /**
     * Reify static method and its native counterpart.
     *
     * @param writer            Class writer
     * @param declInfo          Declaration info
     * @param implClass         Implementing class
     * @param classInfo         Class info
     * @param isDirectInterface Implementing class is a direct interface
     * @param declOwner         Declaration owner
     */
    private void reifyToNativeBridge(ClassWriter writer, DeclInfo declInfo, Type implClass, CxxClassInfo classInfo,
            boolean isDirectInterface, CxxClassInfo declOwner) {
        BCGen.toNativeBridge(writer, declInfo);
        generateStaticNativeMethod(declInfo, implClass, classInfo, isDirectInterface, declOwner);
    }

    /**
     * Reify native part of the bridge method.
     *
     * @param declInfo          Declaration info
     * @param implClass         Implementing class
     * @param classInfo         Class info
     * @param isDirectInterface Implementing class is a direct interface
     * @param declOwner         Declaration owner
     */
    private void generateStaticNativeMethod(DeclInfo declInfo, Type implClass, CxxClassInfo classInfo,
            boolean isDirectInterface, CxxClassInfo declOwner) {
        final boolean isConstructorForPureClass = declInfo instanceof ConstructorInfo
                && ((ConstructorInfo) declInfo).getClassInfo().getClassHasPureVirtualMethod();
        if (isConstructorForPureClass) {
            return;
        }

        source.writeBoth();

        // Write info
        if (classInfo != null) {
            source.commentBlock()
                    .setContents("" + " Interface: " + classInfo.mapperType.getInternalName() + "\n"
                            + "Impl class: " + implClass.getInternalName() + "\n" + "    Method: "
                            + declInfo.getName() + declInfo.getDesc())
                    .write();
        } else {
            source.commentBlock().setContents("" + "Impl class: " + implClass.getInternalName() + "\n"
                    + "    Method: " + declInfo.getName() + declInfo.getDesc()).write();
        }

        // Create function
        final CxxSource.Function function = source.function().setJNIEXPORT(true).setJNICALL(true);
        function.setName(CxxUtils.jnify(implClass, declInfo));
        function.setType(declInfo.getType());
        if (declInfo.getKindInfo().isClassMember()) {
            function.addArg(CxxType.fromType(Type.LONG_TYPE));
            function.setFirstArgAsPeer(true);
        }
        declInfo.getParams().forEach(info -> function.addArg(info.getType()));
        function.write();
        source.writeHeader().w(";").nl();
        source.writeSource().block().write(src -> {
            final List<String> paramNames = function.getParamNames();
            final boolean forwardsExceptions = declInfo instanceof InvokableInfo;

            if (forwardsExceptions) {
                src.w("NATJ_CXX_ENTER_EXC_HANDLER").nl();
            }

            // Cast peer to obj
            if (declInfo.getKindInfo().isClassMember()) {
                if (classInfo == null) {
                    throw new IllegalStateException();
                }
                // Create peer object
                src.w("auto obj = reinterpret_cast<");
                if (declInfo instanceof InvokableInfo && ((InvokableInfo) declInfo).isConst()) {
                    src.w("const ");
                }
                src.w(classInfo.cxxClassName).w("*>(peer);").nl();
            }

            // Convert parameters on demand
            int paramIndex = 0;
            for (ParamInfo paramInfo : declInfo.getParams()) {
                CxxType type = paramInfo.getType();
                String castMethod = type.getJNIToNativeCast();
                if (castMethod != null) {
                    String name = paramNames.get(paramIndex);
                    src.w("auto casted_").w(name).w(" = ").w(castMethod).w("<").w(type.getJNICompatibleNativeType())
                            .w(">(").w(name).w(");").nl();
                    paramNames.set(paramIndex, "casted_" + name);
                }
                ++paramIndex;
            }

            // Prepare return value's variable
            final CxxType returnType = declInfo.getType();
            if (!returnType.isVoid()) {
                // Prepare result's place
                src.w("auto result = ");
                if (returnType.isByReferenceCxxObjectKind() || returnType.isRef()) {
                    // Invoke on object
                    src.w("&(");
                } else if (returnType.isByValueCxxObjectKind()) {
                    src.w("new ").w(returnType.getClassInfoForCxxObjectType().cxxClassName).w("(");
                }
            }

            final int invokeStart = src.getSourceLength();

            // Generate statement
            if (declInfo.getKindInfo().isClassMember()) {
                // Invoke on object
                src.w("obj->");
            }
            if (declInfo.getKindInfo() instanceof InvokableKind) {
                InvokableKind kind = (InvokableKind) declInfo.getKindInfo();
                switch (kind) {
                case FUNCTION: {
                    if (!(declInfo instanceof InvokableInfo)) {
                        throw new IllegalStateException();
                    }
                    InvokableInfo invInfo = (InvokableInfo) declInfo;
                    src.w(invInfo.getNativeIdentifier());
                    passNativeParameters(src, declInfo, paramNames);
                    break;
                }
                case OPERATOR_FUNCTION: {
                    if (!(declInfo instanceof OperatorInfo)) {
                        throw new IllegalStateException();
                    }
                    OperatorInfo invInfo = (OperatorInfo) declInfo;
                    src.w("operator").w(invInfo.getOperatorKind().getName());
                    if (invInfo.getOperatorKind() == OperatorKind.POST_INCREMENT
                            || invInfo.getOperatorKind() == OperatorKind.POST_DECREMENT) {
                        src.w("(");
                        passNativeParameter(src, declInfo, paramNames, 0);
                        src.w(", 0)");

                    } else if (invInfo.getOperatorKind().isExtAssignable()) {
                        passNativeParameters(src, declInfo, paramNames, -1);
                        src.w(" = ");
                        passNativeParameter(src, declInfo, paramNames, -1);

                    } else {
                        passNativeParameters(src, declInfo, paramNames);
                    }
                    break;
                }
                case CONSTRUCTOR: {
                    if (!(declInfo instanceof ConstructorInfo)) {
                        throw new IllegalStateException();
                    }
                    ConstructorInfo invInfo = (ConstructorInfo) declInfo;
                    src.w("new ").w(invInfo.getClassInfo().cxxClassName);
                    passNativeParameters(src, declInfo, paramNames);
                    break;
                }
                case INSTANCE_METHOD: {
                    if (!(declInfo instanceof InvokableInfo)) {
                        throw new IllegalStateException();
                    }
                    InvokableInfo invInfo = (InvokableInfo) declInfo;
                    if (isDirectInterface) {
                        if (classInfo == null) {
                            throw new IllegalStateException();
                        }
                        src.w(classInfo.cxxClassName).w("::");
                    }
                    src.w(invInfo.getNativeIdentifier());
                    passNativeParameters(src, declInfo, paramNames);
                    break;
                }
                case CLASS_METHOD: {
                    if (!(declInfo instanceof InvokableInfo)) {
                        throw new IllegalStateException();
                    }
                    InvokableInfo invInfo = (InvokableInfo) declInfo;
                    if (classInfo == null) {
                        throw new IllegalStateException();
                    }
                    src.w(classInfo.cxxClassName).w("::").w(invInfo.getNativeIdentifier());
                    passNativeParameters(src, declInfo, paramNames);
                    break;
                }
                case OPERATOR: {
                    if (!(declInfo instanceof OperatorInfo)) {
                        throw new IllegalStateException();
                    }
                    OperatorInfo invInfo = (OperatorInfo) declInfo;
                    if (isDirectInterface) {
                        if (classInfo == null) {
                            throw new IllegalStateException();
                        }
                        src.w(classInfo.cxxClassName).w("::");
                    }
                    src.w("operator").w(invInfo.getOperatorKind().getName());
                    if (invInfo.getOperatorKind() == OperatorKind.POST_INCREMENT
                            || invInfo.getOperatorKind() == OperatorKind.POST_DECREMENT) {
                        src.w("(0)");

                    } else if (invInfo.getOperatorKind().isExtAssignable()) {
                        passNativeParameters(src, declInfo, paramNames, -1);
                        src.w(" = ");
                        passNativeParameter(src, declInfo, paramNames, -1);

                    } else {
                        passNativeParameters(src, declInfo, paramNames);
                    }
                    break;
                }
                case CAST_OPERATOR: {
                    if (!(declInfo instanceof CastOperatorInfo)) {
                        throw new IllegalStateException();
                    }
                    if (isDirectInterface) {
                        if (classInfo == null) {
                            throw new IllegalStateException();
                        }
                        src.w(classInfo.cxxClassName).w("::");
                    }
                    src.w("operator ").w(returnType.getNativeType());
                    passNativeParameters(src, declInfo, paramNames);
                    break;
                }
                }

            } else if (declInfo instanceof VariableInfo) {
                VariableInfo varInfo = (VariableInfo) declInfo;
                VariableKind kind = varInfo.getKind();
                switch (kind) {
                case GLOBAL_VARIABLE_GETTER:
                case GLOBAL_VARIABLE_SETTER: {
                    src.w(varInfo.getNativeIdentifier());

                    if (kind.isSetter()) {
                        src.w(" = ");
                        passNativeParameter(src, declInfo, paramNames, 0);
                    }
                    break;
                }
                case CLASS_FIELD_GETTER:
                case CLASS_FIELD_SETTER:
                case INSTANCE_FIELD_GETTER:
                case INSTANCE_FIELD_SETTER: {
                    if (classInfo == null) {
                        throw new IllegalStateException();
                    }
                    src.w((declOwner == null ? classInfo : declOwner).cxxClassName).w("::")
                            .w(varInfo.getNativeIdentifier());

                    if (kind.isSetter()) {
                        src.w(" = ");
                        passNativeParameter(src, declInfo, paramNames, 0);
                    }
                    break;
                }
                }

            } else {
                throw new IllegalStateException();
            }

            final int invokeEnd = src.getSourceLength();
            final String invokeString = src.getSourceSubstring(invokeStart, invokeEnd);

            // Close optional '&(' with ')'
            if (returnType.isByReferenceCxxObjectKind() || returnType.isRef()) {
                // Invoke on object
                src.w(")");
            } else if (returnType.isByValueCxxObjectKind()) {
                src.w(")");
            }
            src.w(";").nl();

            if (configuration.genCheckCxxReturnTypeCorrectness) {
                // Do static assert on return type
                if (!returnType.isVoid()) {
                    src.w("static_assert(natj::assert_type<decltype(result), ");
                    if (declInfo instanceof ConstructorInfo
                            && ((ConstructorInfo) declInfo).getClassInfo().isInherited) {
                        src.w(((ConstructorInfo) declInfo).getClassInfo().cxxClassName).w("*");

                    } else if (returnType.isConstCxxObjectKind() && returnType.isByValueCxxObjectKind()) {
                        src.w(returnType.getClassInfoForCxxObjectType().cxxClassName).w("*");

                    } else {
                        src.w(returnType.getJNICompatibleNativeType());
                    }
                    src.w(">::value, \"return type mismatch\");").nl();

                    if (!(declInfo instanceof ConstructorInfo) && returnType.isCxxObjectKind()) {
                        if (returnType.isByValueCxxObjectKind()) {
                            src.w("static_assert(!std::is_pointer<decltype(" + invokeString + ")>::value "
                                    + "&& !std::is_reference<decltype(" + invokeString + ")>::value, "
                                    + "\"by value is invalid\");").nl();

                        } else if (returnType.isByReferenceCxxObjectKind()) {
                            src.w("static_assert(std::is_reference<decltype(" + invokeString + ")>::value, "
                                    + "\"by reference is invalid\");").nl();

                        } else {
                            src.w("static_assert(std::is_pointer<decltype(" + invokeString + ")>::value, "
                                    + "\"by address is invalid\");").nl();
                        }
                    }
                }
            }

            // Return
            if (!returnType.isVoid()) {
                src.w("return ");
                String castMethod = returnType.getNativeToJNICast();
                if (declInfo instanceof ConstructorInfo
                        && ((ConstructorInfo) declInfo).getClassInfo().isInherited) {
                    castMethod = "reinterpret_cast";
                }
                if (castMethod != null) {
                    src.w(castMethod).w("<").w(returnType.getNativeJNIType()).w(">(result)");
                } else {
                    src.w("result");
                }
                src.w(";").nl();

                if (forwardsExceptions) {
                    src.w("NATJ_CXX_EXIT_EXC_HANDLER_RET").nl();
                }
            } else {

                if (forwardsExceptions) {
                    src.w("NATJ_CXX_EXIT_EXC_HANDLER").nl();
                }
            }
        });
        source.writeBoth().nl();
    }

    /**
     * In C++ code, pass a parameter.
     *
     * @param src        C++ source buffer
     * @param declInfo   Declaration info
     * @param paramNames Parameter names
     * @param paramIndex Parameter index, negative number to reverse index
     */
    private void passNativeParameter(CxxSource src, DeclInfo declInfo, List<String> paramNames, int paramIndex) {
        if (paramIndex < 0) {
            paramIndex = declInfo.getParams().size() + paramIndex;
        }
        ParamInfo paramInfo = declInfo.getParams().get(paramIndex);
        final CxxType type = paramInfo.getType();
        if (type.isByValueCxxObjectKind() || type.isByReferenceCxxObjectKind() || type.isRef()) {
            src.w("*");
        }
        src.w(paramNames.get(paramIndex));
    }

    /**
     * In C++ code, pass all parameters.
     *
     * @param src        C++ source buffer
     * @param declInfo   Declaration info
     * @param paramNames Parameter names
     */
    private void passNativeParameters(CxxSource src, DeclInfo declInfo, List<String> paramNames) {
        passNativeParameters(src, declInfo, paramNames, Integer.MAX_VALUE);
    }

    /**
     * In C++ code, pass all parameters.
     *
     * @param src        C++ source buffer
     * @param declInfo   Declaration info
     * @param paramNames Parameter names
     * @param limit      Maximum number of parameters to pass
     */
    private void passNativeParameters(CxxSource src, DeclInfo declInfo, List<String> paramNames, int limit) {
        src.w("(");
        int size = declInfo.getParams().size();
        if (limit < 0) {
            size += limit;
        } else {
            size = Math.min(size, limit);
        }
        for (int paramIndex = 0; paramIndex < size; ++paramIndex) {
            if (paramIndex > 0) {
                src.w(", ");
            }
            passNativeParameter(src, declInfo, paramNames, paramIndex);
        }
        src.w(")");
    }
}