Java tutorial
/* 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(")"); } }