diff --git a/CHANGELOG.md b/CHANGELOG.md index bf229cb00..1f3dc81c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ + * Add `Info.upcast` to support class hierarchies with virtual inheritance ([pull #671](https://github.com/bytedeco/javacpp/pull/671)) * Pick up `@Adapter`, `@SharedPtr`, etc annotations on `allocate()` as well ([pull #668](https://github.com/bytedeco/javacpp/pull/668)) * Provide `@Virtual(subclasses=false)` to prevent `Generator` from subclassing subclasses ([pull #660](https://github.com/bytedeco/javacpp/pull/660)) * Fix `Loader.getPlatform()` detection for `linux-armhf` with Temurin JDK ([issue bytedeco/javacv#2001](https://github.com/bytedeco/javacv/issues/2001)) diff --git a/src/main/java/org/bytedeco/javacpp/annotation/Virtual.java b/src/main/java/org/bytedeco/javacpp/annotation/Virtual.java index 1743bb6d8..7b2568816 100644 --- a/src/main/java/org/bytedeco/javacpp/annotation/Virtual.java +++ b/src/main/java/org/bytedeco/javacpp/annotation/Virtual.java @@ -21,4 +21,5 @@ /** Pure (abstract) or not. */ boolean value() default false; boolean subclasses() default true; -} \ No newline at end of file + String method() default ""; +} diff --git a/src/main/java/org/bytedeco/javacpp/tools/Context.java b/src/main/java/org/bytedeco/javacpp/tools/Context.java index fba7051ee..2e46c2cec 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Context.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Context.java @@ -47,6 +47,7 @@ class Context { inaccessible = c.inaccessible; beanify = c.beanify; objectify = c.objectify; + upcast = c.upcast; virtualize = c.virtualize; variable = c.variable; infoMap = c.infoMap; @@ -65,6 +66,7 @@ class Context { boolean inaccessible = false; boolean beanify = false; boolean objectify = false; + boolean upcast = false; boolean virtualize = false; Declarator variable = null; InfoMap infoMap = null; diff --git a/src/main/java/org/bytedeco/javacpp/tools/Generator.java b/src/main/java/org/bytedeco/javacpp/tools/Generator.java index 57c111d52..4f4382384 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Generator.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Generator.java @@ -3406,8 +3406,10 @@ void callback(Class cls, Method callbackMethod, String callbackName, int allo } if (methodInfo != null) { + Virtual virtual = callbackMethod.getAnnotation(Virtual.class); + String methodName = (virtual != null && virtual.method().length() > 0) ? virtual.method() : methodInfo.method.getName(); out.println(" if (" + fieldName + " == NULL) {"); - out.println(" " + fieldName + " = JavaCPP_getMethodID(env, " + jclasses.index(cls) + ", \"" + methodInfo.method.getName() + "\", \"(" + + out.println(" " + fieldName + " = JavaCPP_getMethodID(env, " + jclasses.index(cls) + ", \"" + methodName + "\", \"(" + signature(methodInfo.method.getParameterTypes()) + ")" + signature(methodInfo.method.getReturnType()) + "\");"); out.println(" }"); out.println(" jmethodID mid = " + fieldName + ";"); diff --git a/src/main/java/org/bytedeco/javacpp/tools/Info.java b/src/main/java/org/bytedeco/javacpp/tools/Info.java index 0606350a2..57341f6f6 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Info.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Info.java @@ -61,6 +61,7 @@ public Info(Info i) { skip = i.skip; skipDefaults = i.skipDefaults; purify = i.purify; + upcast = i.upcast; virtualize = i.virtualize; base = i.base; cppText = i.cppText; @@ -96,7 +97,7 @@ public Info(Info i) { * To use as keys in maps, etc, intern() must be called on instances returned from native code. */ boolean enumerate = false; /** Outputs declarations for this class into their subclasses as well. - * Also adds methods for explicit casting, as done for multiple inheritance by default. */ + * Also adds methods for upcasting, as done for multiple inheritance by default. */ boolean flatten = false; /** Maps friend functions. Only functions having in their argument list an instance of the class they are friend * of are currently supported. They are mapped as instance methods of the class. */ @@ -116,6 +117,9 @@ public Info(Info i) { boolean skipDefaults = false; /** Forces a class to be treated as if it were abstract. */ boolean purify = false; + /** Whether a static_cast is needed to upcast a pointer to this cppName. + * This is necessary for polymorphic classes that are virtually inherited from. */ + boolean upcast = false; /** Annotates virtual functions with @{@link Virtual} and adds appropriate constructors. */ boolean virtualize = false; /** Allows to override the base class of {@link #pointerTypes}. Defaults to {@link Pointer}. */ @@ -156,6 +160,8 @@ public Info(Info i) { public Info skipDefaults(boolean skipDefaults) { this.skipDefaults = skipDefaults; return this; } public Info purify() { this.purify = true; return this; } public Info purify(boolean purify) { this.purify = purify; return this; } + public Info upcast() { this.upcast = true; return this; } + public Info upcast(boolean upcast) { this.upcast = upcast; return this; } public Info virtualize() { this.virtualize = true; return this; } public Info virtualize(boolean virtualize) { this.virtualize = virtualize; return this; } public Info base(String base) { this.base = base; return this; } diff --git a/src/main/java/org/bytedeco/javacpp/tools/Parser.java b/src/main/java/org/bytedeco/javacpp/tools/Parser.java index d0a0851a9..617972948 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Parser.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Parser.java @@ -83,6 +83,7 @@ public Parser(Logger logger, Properties properties, String encoding, String line this.properties = p.properties; this.encoding = p.encoding; this.infoMap = p.infoMap; + this.upcasts = p.upcasts; Token t = p.tokens != null ? p.tokens.get() : Token.EOF; this.tokens = new TokenIndexer(infoMap, new Tokenizer(text, t.file, t.lineNumber).tokenize(), false); this.lineSeparator = p.lineSeparator; @@ -95,6 +96,16 @@ public Parser(Logger logger, Properties properties, String encoding, String line InfoMap leafInfoMap = null; TokenIndexer tokens = null; String lineSeparator = null; + Set upcasts = new HashSet<>(); + + static String removeAnnotations(String s) { + return s.substring(s.lastIndexOf(' ') + 1); + } + + static String upcastMethodName(String javaName) { + String shortName = javaName.substring(javaName.lastIndexOf('.') + 1); + return "as" + Character.toUpperCase(shortName.charAt(0)) + shortName.substring(1); + } String translate(String text) { Info info = infoMap.getFirst(text); @@ -356,9 +367,9 @@ void containers(Context context, DeclarationList declList) throws ParserExceptio String indexAnnotation = dim == 0 ? "@MemberGetter " : "@Index(" + (dim > 1 ? "value = " + dim + ", " : "") + "function = \"at\") "; decl.text += "\n" + " " + indexAnnotation + "public native " + firstType.annotations + firstType.javaName + " first(" + params + ");" - + " public native " + containerType.javaName + " first(" + params + separator + firstType.javaName.substring(firstType.javaName.lastIndexOf(' ') + 1) + " first);\n" + + " public native " + containerType.javaName + " first(" + params + separator + removeAnnotations(firstType.javaName) + " first);\n" + " " + indexAnnotation + "public native " + secondType.annotations + secondType.javaName + " second(" + params + "); " - + " public native " + containerType.javaName + " second(" + params + separator + secondType.javaName.substring(secondType.javaName.lastIndexOf(' ') + 1) + " second);\n"; + + " public native " + containerType.javaName + " second(" + params + separator + removeAnnotations(secondType.javaName) + " second);\n"; for (int i = 1; !constant && firstType.javaNames != null && i < firstType.javaNames.length; i++) { decl.text += " @MemberSetter @Index" + indexFunction + " public native " + containerType.javaName + " first(" + params + separator + firstType.annotations + firstType.javaNames[i] + " first);\n"; } @@ -370,7 +381,7 @@ void containers(Context context, DeclarationList declList) throws ParserExceptio decl.text += "\n" + " @Index" + indexFunction + " public native " + valueType.annotations + valueType.javaName + " get(" + params + ");\n"; if (!constant) { - decl.text += " public native " + containerType.javaName + " put(" + params + separator + valueType.javaName.substring(valueType.javaName.lastIndexOf(' ') + 1) + " value);\n"; + decl.text += " public native " + containerType.javaName + " put(" + params + separator + removeAnnotations(valueType.javaName) + " value);\n"; } for (int i = 1; !constant && valueType.javaNames != null && i < valueType.javaNames.length; i++) { decl.text += " @ValueSetter @Index" + indexFunction + " public native " + containerType.javaName + " put(" + params + separator + valueType.annotations + valueType.javaNames[i] + " value);\n"; @@ -432,7 +443,7 @@ void containers(Context context, DeclarationList declList) throws ParserExceptio + " }\n"; } if (resizable) { - valueType.javaName = valueType.javaName.substring(valueType.javaName.lastIndexOf(' ') + 1); // get rid of any annotations + valueType.javaName = removeAnnotations(valueType.javaName); decl.text += "\n" + " public " + valueType.javaName + arrayBrackets + "[] get() {\n"; @@ -470,8 +481,8 @@ void containers(Context context, DeclarationList declList) throws ParserExceptio for (int n = 0; n < firstNames.length || n < secondNames.length; n++) { String firstName = firstNames[Math.min(n, firstNames.length - 1)]; String secondName = secondNames[Math.min(n, secondNames.length - 1)]; - firstName = firstName.substring(firstName.lastIndexOf(' ') + 1); // get rid of any annotations - secondName = secondName.substring(secondName.lastIndexOf(' ') + 1); + firstName = removeAnnotations(firstName); + secondName = removeAnnotations(secondName); decl.text += "\n" + " public " + containerType.javaName + " put(" + firstName + brackets + " firstValue, " + secondName + brackets + " secondValue) {\n"; @@ -499,7 +510,7 @@ void containers(Context context, DeclarationList declList) throws ParserExceptio } else if (resizable && firstType == null && secondType == null) { boolean first = true; for (String javaName : valueType.javaNames != null ? valueType.javaNames : new String[] {valueType.javaName}) { - javaName = javaName.substring(javaName.lastIndexOf(' ') + 1); // get rid of any annotations + javaName = removeAnnotations(javaName); decl.text += "\n"; if (dim < 2) { if (first) { @@ -938,9 +949,13 @@ Type type(Context context, boolean definition) throws ParserException { } } - if (info != null && info.cppTypes != null && info.cppTypes.length > 0) { + if (info != null && info.cppTypes != null && info.cppTypes.length > 0 && !info.cppTypes[0].equals(type.cppName)) { // use user defined type type.cppName = info.cppTypes[0]; + // arguments won't match the new cppName, we might need: + // Type type2 = new Parser(this, type.cppName).type(context); + // type.arguments = type2.arguments; + // if arguments is used below. } // remove const, * and & after user defined substitution for consistency @@ -1031,6 +1046,9 @@ else if (cppNameSplit.size() > 1 && groupNameSplit.size() == 1) } type.javaName = context.shorten(type.javaName); } + if (info != null && info.upcast) { + upcasts.add(type.javaName); + } return type; } @@ -1616,7 +1634,7 @@ Declarator declarator(Context context, String defaultName, int infoNumber, boole // temporary name to be replaced in typedef() functionType = "null"; } - functionType = functionType.substring(functionType.lastIndexOf(' ') + 1); // get rid of pointer annotations + functionType = removeAnnotations(functionType); if (!functionType.equals("Pointer") && !functionType.equals("long")) { definition.type = new Type(functionType); for (Info info2 : infoMap.get("function/pointers")) { @@ -2102,6 +2120,9 @@ Parameters parameters(Context context, int infoNumber, boolean useDefaults) thro params.signature += '_'; params.signature += dcl.type.signature(); params.names += (count > 1 ? ", " : "") + paramName; + if (upcasts.contains(dcl.type.javaName)) { + params.names += '.' + upcastMethodName(removeAnnotations(dcl.type.javaName)) + "()"; + } if (dcl.javaName.startsWith("arg")) { try { count = Integer.parseInt(dcl.javaName.substring(3)) + 1; @@ -2215,12 +2236,11 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti dcl.type = type; dcl.parameters = params; dcl.cppName = type.cppName; - dcl.javaName = type.javaName.substring(type.javaName.lastIndexOf(' ') + 1); + dcl.javaName = removeAnnotations(type.javaName); if (type.operator) { - String shortName = dcl.javaName.substring(dcl.javaName.lastIndexOf('.') + 1); dcl.cppName = "operator " + (dcl.type.constValue ? "const " : "") + dcl.type.cppName + (dcl.type.indirections > 0 ? "*" : dcl.type.reference ? "&" : ""); - dcl.javaName = "as" + Character.toUpperCase(shortName.charAt(0)) + shortName.substring(1); + dcl.javaName = upcastMethodName(dcl.javaName); } dcl.signature = dcl.javaName + params.signature; } else { @@ -2373,12 +2393,11 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti dcl.type = type; dcl.parameters = params; dcl.cppName = type.cppName; - dcl.javaName = type.javaName.substring(type.javaName.lastIndexOf(' ') + 1); + dcl.javaName = removeAnnotations(type.javaName); if (type.operator) { - String shortName = dcl.javaName.substring(dcl.javaName.lastIndexOf('.') + 1); dcl.cppName = "operator " + (dcl.type.constValue ? "const " : "") + dcl.type.cppName + (dcl.type.indirections > 0 ? "*" : dcl.type.reference ? "&" : ""); - dcl.javaName = "as" + Character.toUpperCase(shortName.charAt(0)) + shortName.substring(1); + dcl.javaName = upcastMethodName(dcl.javaName); } dcl.signature = dcl.javaName + params.signature; for (Token token = tokens.get(); !token.match(Token.EOF); token = tokens.get()) { @@ -2515,10 +2534,25 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti } } + boolean staticMethod = type.staticMember || type.friend || context.javaName == null; + boolean needUpcast = !type.constructor && !staticMethod && context.upcast; + if (!type.constructor) { + for (Declarator paramDecl : dcl.parameters.declarators) { + if (paramDecl != null) { + needUpcast |= upcasts.contains(paramDecl.type.javaName); + } + } + } + // add @Virtual annotation on user request only, inherited through context if (context.virtualize && !type.annotations.contains("@Virtual")) { - modifiers = "@Virtual" + (decl.abstractMember ? "(true) " : " ") - + (context.inaccessible ? "protected native " : "public native "); + modifiers = "@Virtual"; + if (needUpcast) { + modifiers += (decl.abstractMember ? "(value = true, " : "(") + "method = \"" + dcl.javaName + "\") "; + } else { + modifiers += (decl.abstractMember ? "(true) " : " "); + } + modifiers += (context.inaccessible ? "protected native " : "public native "); } // compose the text of the declaration with the info we got up until this point @@ -2536,7 +2570,32 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti decl.text += "public " + context.shorten(context.javaName) + dcl.parameters.list + " { super((Pointer)null); allocate" + params.names + "; }\n" + type.annotations + "private native void allocate" + dcl.parameters.list + ";\n"; } else { - decl.text += modifiers + type.annotations + context.shorten(type.javaName) + " " + dcl.javaName + dcl.parameters.list + ";\n"; + String modifiers2 = modifiers; + if (needUpcast) { + if (!type.annotations.contains("@Name")) { + type.annotations += "@Name(\"" + localName2 + "\") "; + } + Pattern pattern = Pattern.compile("\\b(private|protected|public)\\b"); + Matcher matcher = pattern.matcher(modifiers2); + modifiers2 = matcher.replaceFirst("private"); + String accessModifier = matcher.group(1); + + // Rebuild a parameters list without the annotations. + StringBuilder sb = new StringBuilder(); + for (Declarator param : dcl.parameters.declarators) { + if (sb.length() > 0) { + sb.append(", "); + } + sb.append(removeAnnotations(param.type.javaName)).append(" ").append(param.javaName); + } + decl.text += accessModifier + " " + (staticMethod ? "static " : "") + + removeAnnotations(type.javaName) + " " + dcl.javaName + "(" + sb + ") { " + + (type.javaName.equals("void") ? "" : "return ") + + (context.upcast && !staticMethod ? upcastMethodName(context.javaName) + "()." : "") + + "_" + dcl.javaName + (dcl.parameters.names == null ? "()" : dcl.parameters.names) + "; }\n"; + dcl.javaName = "_" + dcl.javaName; + } + decl.text += modifiers2 + type.annotations + context.shorten(type.javaName) + " " + dcl.javaName + dcl.parameters.list + ";\n"; } decl.signature = dcl.signature; @@ -2738,13 +2797,12 @@ boolean variable(Context context, DeclarationList declList) throws ParserExcepti decl.text += "\n" + nameAnnotation + "@MemberSetter " + modifiers + setterType + "set" + capitalizedJavaName + "(" + indices + dcl.type.annotations + dcl.type.javaName + " setter);"; } else { - String javaTypeWithoutAnnotations = dcl.type.javaName.substring(dcl.type.javaName.lastIndexOf(" ") + 1); - decl.text += " " + modifiers + setterType + javaName + "(" + indices + javaTypeWithoutAnnotations + " setter);"; + decl.text += " " + modifiers + setterType + javaName + "(" + indices + removeAnnotations(dcl.type.javaName) + " setter);"; } } decl.text += "\n"; if ((dcl.type.constValue || dcl.constPointer || dcl.type.constExpr) && dcl.type.staticMember && indices.length() == 0) { - String rawType = dcl.type.javaName.substring(dcl.type.javaName.lastIndexOf(' ') + 1); + String rawType = removeAnnotations(dcl.type.javaName); if ("byte".equals(rawType) || "short".equals(rawType) || "int".equals(rawType) || "long".equals(rawType) || "float".equals(rawType) || "double".equals(rawType) || "char".equals(rawType) || "boolean".equals(rawType)) { // only mind of what looks like constants that we can keep without hogging memory @@ -3233,6 +3291,29 @@ boolean using(Context context, DeclarationList declList) throws ParserException return true; } + String upcast(Type from, Type to, boolean override) { + String ecmn = upcastMethodName(to.javaName); + String res = " " + (override ? "@Override " : "") + "public " + to.javaName + " " + ecmn + "() { return " + ecmn + "(this); }\n" + + " @Namespace public static native "; + /* We generate the adapter-aware version of the method when the target class has any annotation, + assuming that this annotation is @SharedPtr or some other adapter storing a share_ptr in owner. + This is the only use case for now. We can generalize to any adapter if the need arises and call + some cast function on the adapter instead of static_pointer_cast. */ + List split = Templates.splitNamespace(to.cppName); + String constructorName = to.cppName + "::" + Templates.strip(split.get(split.size() - 1)); + Info info = infoMap.getFirst(constructorName); + String annotations = ""; + if (info != null && info.annotations != null) { + for (String s : info.annotations) { + annotations += s + " "; + } + res += annotations + "@Name(\"SHARED_PTR_NAMESPACE::static_pointer_cast<" + to.cppName + ", " + from.cppName + ">\") "; + } else { + res += "@Name(\"static_cast<" + to.cppName + "*>\") "; + } + return res + to.javaName + " " + ecmn + "(" + annotations + from.javaName + " pointer);\n"; + } + boolean group(Context context, DeclarationList declList) throws ParserException { int backIndex = tokens.index; String spacing = tokens.get().spacing; @@ -3381,7 +3462,7 @@ boolean group(Context context, DeclarationList declList) throws ParserException declList.add(decl); return true; } else if (info != null && info.pointerTypes != null && info.pointerTypes.length > 0) { - type.javaName = context.constName != null ? context.constName : info.pointerTypes[0].substring(info.pointerTypes[0].lastIndexOf(" ") + 1); + type.javaName = context.constName != null ? context.constName : removeAnnotations(info.pointerTypes[0]); name = context.shorten(type.javaName); } else if (info == null && !friend) { if (type.javaName.length() > 0 && context.javaName != null) { @@ -3405,13 +3486,18 @@ boolean group(Context context, DeclarationList declList) throws ParserException for (Type t : baseClasses) { Info baseInfo = infoMap.getFirst(t.cppName); if (!t.javaName.equals("Pointer") && (baseInfo == null || !baseInfo.skip)) { - String shortName = t.javaName.substring(t.javaName.lastIndexOf('.') + 1); - casts += " public " + t.javaName + " as" + shortName + "() { return as" + shortName + "(this); }\n" - + " @Namespace public static native @Name(\"static_cast<" + t.cppName + "*>\") " - + t.javaName + " as" + shortName + "(" + type.javaName + " pointer);\n"; + casts += upcast(type, t, false); } } } + if (upcasts.contains(type.javaName)) { + casts += " public " + type.javaName + " " + upcastMethodName(type.javaName) + "() { return this; }\n"; + } + + if (upcasts.contains(base.javaName)) { + casts += upcast(type, base, true); + } + decl.signature = type.javaName; tokens.index = startIndex; String shortName = name.substring(name.lastIndexOf('.') + 1); @@ -3481,6 +3567,7 @@ boolean group(Context context, DeclarationList declList) throws ParserException ctx.immutable = true; if (info.beanify) ctx.beanify = true; + ctx.upcast = info.upcast; } ctx.baseType = base.cppName; @@ -4122,7 +4209,7 @@ void declarations(Context context, DeclarationList declList) throws ParserExcept if (info.cppNames != null && info.cppNames.length > 0 && info.cppNames[0].startsWith("const ") && info.pointerTypes != null && info.pointerTypes.length > 0) { ctx = new Context(ctx); - ctx.constName = info.pointerTypes[0].substring(info.pointerTypes[0].lastIndexOf(" ") + 1); + ctx.constName = removeAnnotations(info.pointerTypes[0]); ctx.constBaseName = info.base != null ? info.base : "Pointer"; } tokens.index = startIndex;