diff --git a/src/main/java/org/bytedeco/javacpp/tools/Context.java b/src/main/java/org/bytedeco/javacpp/tools/Context.java index 2dd2e3cfc..6f547fd4d 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Context.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Context.java @@ -71,6 +71,7 @@ class Context { TemplateMap templateMap = null; List usingList = null; Map namespaceMap = null; + CopiedDeclarations copiedDeclarations; /** Return all likely combinations of namespaces and template arguments for this C++ type */ String[] qualify(String cppName) { diff --git a/src/main/java/org/bytedeco/javacpp/tools/CopiedDeclarations.java b/src/main/java/org/bytedeco/javacpp/tools/CopiedDeclarations.java new file mode 100644 index 000000000..3ff15af74 --- /dev/null +++ b/src/main/java/org/bytedeco/javacpp/tools/CopiedDeclarations.java @@ -0,0 +1,25 @@ +package org.bytedeco.javacpp.tools; + +import java.util.ArrayList; + +class CopiedDeclarations extends ArrayList { + void add(Declaration decl, String fullname, String modifiers, Context context) { + add(new CopiedDeclaration(decl, fullname, modifiers, context.namespace, context.templateMap)); + } + + static class CopiedDeclaration { + final Declaration decl; + final String fullname; + final String modifiers; + final String namespace; + final TemplateMap templateMap; + + CopiedDeclaration(Declaration decl, String fullname, String modifiers, String namespace, TemplateMap templateMap) { + this.decl = decl; + this.fullname = fullname; + this.modifiers = modifiers; + this.namespace = namespace; + this.templateMap = templateMap; + } + } +} diff --git a/src/main/java/org/bytedeco/javacpp/tools/Declaration.java b/src/main/java/org/bytedeco/javacpp/tools/Declaration.java index 510ffe516..2d58141f3 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Declaration.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Declaration.java @@ -33,6 +33,25 @@ class Declaration { incomplete = false, function = false, variable = false, comment = false, custom = false; String signature = "", text = ""; + public Declaration() { + } + + Declaration(Declaration src) { + // Shallow copy is enough for the current usage + type = src.type; + declarator = src.declarator; + abstractMember = src.abstractMember; + constMember = src.constMember; + inaccessible = src.inaccessible; + incomplete = src.incomplete; + function = src.function; + variable = src.variable; + comment = src.comment; + custom = src.custom; + signature = src.signature; + text = src.text; + } + public String toString() { return text; } diff --git a/src/main/java/org/bytedeco/javacpp/tools/Info.java b/src/main/java/org/bytedeco/javacpp/tools/Info.java index 0606350a2..32cc7d9ab 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Info.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Info.java @@ -27,6 +27,10 @@ import org.bytedeco.javacpp.annotation.Cast; import org.bytedeco.javacpp.annotation.Virtual; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + /** * Holds information useful to the {@link Parser} and associated with C++ identifiers. * Info objects are meant to be added by the user to an {@link InfoMap} passed as @@ -65,6 +69,7 @@ public Info(Info i) { base = i.base; cppText = i.cppText; javaText = i.javaText; + copyFrom = i.copyFrom == null ? null : new ArrayList<>(i.copyFrom); } /** A list of C++ identifiers, expressions, or header filenames to which this info is to be bound. @@ -124,6 +129,8 @@ public Info(Info i) { String cppText = null; /** Outputs the given code, instead of the result parsed from the declaration of C++ identifiers. */ String javaText = null; + /** List of cpp names (class, struct, member function) the declarations of which we want to copy in this class or struct. */ + List copyFrom = null; public Info cppNames(String... cppNames) { this.cppNames = cppNames; return this; } public Info javaNames(String... javaNames) { this.javaNames = javaNames; return this; } @@ -161,4 +168,5 @@ public Info(Info i) { public Info base(String base) { this.base = base; return this; } public Info cppText(String cppText) { this.cppText = cppText; return this; } public Info javaText(String javaText) { this.javaText = javaText; return this; } + public Info copyFrom(String... cppNames) { this.copyFrom = Arrays.asList(cppNames); return this; } } diff --git a/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java b/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java index 89b7b47fe..72aae9d99 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java +++ b/src/main/java/org/bytedeco/javacpp/tools/InfoMap.java @@ -343,4 +343,5 @@ public InfoMap put(Info info) { public InfoMap putFirst(Info info) { return put(0, info); } + } diff --git a/src/main/java/org/bytedeco/javacpp/tools/Parser.java b/src/main/java/org/bytedeco/javacpp/tools/Parser.java index 90beb8c86..aff39b75d 100644 --- a/src/main/java/org/bytedeco/javacpp/tools/Parser.java +++ b/src/main/java/org/bytedeco/javacpp/tools/Parser.java @@ -95,6 +95,10 @@ public Parser(Logger logger, Properties properties, String encoding, String line InfoMap leafInfoMap = null; TokenIndexer tokens = null; String lineSeparator = null; + HashSet polymorphicClasses = new HashSet<>(); // Contains Java names + // Keys of copiedDeclarations are the cpp names of sources we must copy declarations from: + // polymorphic class, or values of copyFrom Info (template instances in case of templated class or functions). + HashMap copiedDeclarations = new HashMap<>(); String translate(String text) { Info info = infoMap.getFirst(text); @@ -2249,8 +2253,8 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti return false; } - int namespace = dcl.cppName.lastIndexOf("::"); - if (context.namespace != null && namespace < 0) { + boolean hasNamespace = dcl.cppName.contains("::"); + if (context.namespace != null && !hasNamespace) { dcl.cppName = context.namespace + "::" + dcl.cppName; } Info info = null, fullInfo = null; @@ -2442,8 +2446,8 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti } else { dcl = declarator(context, null, n / 2, (info == null || !info.skipDefaults) && n % 2 != 0, 0, false, false); type = dcl.type; - namespace = dcl.cppName.lastIndexOf("::"); - if (context.namespace != null && namespace < 0) { + hasNamespace = dcl.cppName.contains("::"); + if (context.namespace != null && !hasNamespace) { dcl.cppName = context.namespace + "::" + dcl.cppName; } } @@ -2626,8 +2630,29 @@ boolean function(Context context, DeclarationList declList) throws ParserExcepti if (declList.add(decl, fullname)) { first = false; if (extraDecl != null) declList.add(extraDecl); + // Do not copy constructors unless specifically asked for with Info. + // Do not copy static functions. The copy would probably need changes in the type of its argument + // to be useful anyway. + // We don't copy extraDecl either that is only used for friends and that calls a static method. + if (!decl.declarator.type.staticMember && !decl.inaccessible) { + final String key; + if (type.constructor) { + int template = dcl.cppName.indexOf('<'); + if (template < 0) template = dcl.cppName.length(); + int namespace = dcl.cppName.lastIndexOf("::", template); + if (namespace < 0) namespace = -2; + key = dcl.cppName + "::" + dcl.cppName.substring(namespace + 2, template); + } else { + key = dcl.cppName; + } + CopiedDeclarations cd = copiedDeclarations.get(key); + if (cd != null) cd.add(decl, fullname, modifiers, context); + if (context.copiedDeclarations != null && !type.constructor) + context.copiedDeclarations.add(decl, fullname, modifiers, context); + } } if (type.virtual && context.virtualize) { + // Prevent creation of overloads, that are not supported by the generated C++ proxy class. break; } } else if (found && n / 2 > 0 && n % 2 == 0 && n / 2 > Math.max(dcl.infoNumber, dcl.parameters.infoNumber)) { @@ -3292,17 +3317,22 @@ boolean group(Context context, DeclarationList declList) throws ParserException decl.text = type.annotations; String name = type.javaName; boolean anonymous = !typedef && type.cppName.length() == 0, derivedClass = false, skipBase = false; + List copyFrom = new ArrayList<>(); + List flattenFrom = new ArrayList<>(); if (type.cppName.length() > 0 && tokens.get().match(':')) { derivedClass = true; + boolean virtualInheritance = false; + boolean accessible = !ctx.inaccessible; for (Token token = tokens.next(); !token.match(Token.EOF); token = tokens.next()) { - boolean accessible = !ctx.inaccessible; if (token.match(Token.VIRTUAL)) { + virtualInheritance = true; continue; } else if (token.match(Token.PRIVATE, Token.PROTECTED, Token.PUBLIC)) { accessible = token.match(Token.PUBLIC); - tokens.next(); + continue; } Type t = type(context); + t.virtual = virtualInheritance; Info info = infoMap.getFirst(t.cppName); if (info != null && info.skip) { skipBase = true; @@ -3313,6 +3343,8 @@ boolean group(Context context, DeclarationList declList) throws ParserException if (tokens.get().expect(',', '{').match('{')) { break; } + virtualInheritance = false; + accessible = !ctx.inaccessible; } } if (typedef && type.indirections > 0) { @@ -3409,17 +3441,35 @@ boolean group(Context context, DeclarationList declList) throws ParserException } infoMap.put(info = new Info(type.cppName).pointerTypes(type.javaName)); } - Type base = new Type("Pointer"); + + // Choose the base Java type: first base C++ type which is not flattened and that we can inherit from. + // Keep the others in baseClasses to generate asX() casts and fill copyFrom and flattenFrom. + // If no C++ base type suits, use Pointer. + boolean polymorphic = false; + Type base = null; Iterator it = baseClasses.iterator(); while (it.hasNext()) { Type next = it.next(); Info nextInfo = infoMap.getFirst(next.cppName); - if (nextInfo == null || !nextInfo.flatten) { + if (nextInfo != null) { + boolean nextPolymorphic = polymorphicClasses.contains(next.javaName); + polymorphic |= nextPolymorphic; + if (nextInfo.flatten) { + flattenFrom.add(next); + continue; + } else if (nextPolymorphic && next.virtual) { + // Virtual inheritance to a polymorphic class, we cannot inherit in Java, copy declarations instead. + copyFrom.add(next.cppName); + continue; + } + } + if (base == null) { base = next; it.remove(); - break; } } + if (base == null) base = new Type("Pointer"); + String casts = ""; if (baseClasses.size() > 0) { for (Type t : baseClasses) { @@ -3501,9 +3551,16 @@ boolean group(Context context, DeclarationList declList) throws ParserException ctx.immutable = true; if (info.beanify) ctx.beanify = true; + if (info.copyFrom != null) + copyFrom.addAll(info.copyFrom); } ctx.baseType = base.cppName; + /* Always copy member declarations. The copy will be saved in copiedDeclarations if this class turns out to be + * polymorphic (and in case another class parsed later virtually inherits from it) or if the used asked for + * copy by listing this class in an Info.copyFrom. */ + ctx.copiedDeclarations = new CopiedDeclarations(); + DeclarationList declList2 = new DeclarationList(); if (variables.size() == 0) { declarations(ctx, declList2); @@ -3524,11 +3581,58 @@ boolean group(Context context, DeclarationList declList) throws ParserException ctx.variable = var; declarations(ctx, declList2); } + + String thisNamespace = context.namespace != null && context.javaName == null ? context.namespace : null; + for (String cf: copyFrom) { + CopiedDeclarations cp = copiedDeclarations.get(cf); + if (cp != null) { + declList2.context.inaccessible = false; + // Recompose the text of copied declarations, change the class name for constructor + // Adjust @Virtual. + // Probably other modifications are necessary. + for (CopiedDeclarations.CopiedDeclaration cd : cp) { + Declaration d = new Declaration(cd.decl); + final String funcName = (d.declarator.type != null && d.declarator.type.constructor) ? + shortName : ctx.shorten(d.declarator.javaName); + Info infoCopyTo = infoMap.getFirst(ctx.namespace == null ? funcName : ctx.namespace + "::" + funcName); + String modifiers = cd.modifiers.replace("@Virtual", ""); + if (infoCopyTo != null) { + if (infoCopyTo.virtualize) modifiers = "@Virtual " + modifiers; + if (infoCopyTo.annotations != null) { + for (String ann : infoCopyTo.annotations) + modifiers += ann + " "; + } + } + if (infoCopyTo != null && infoCopyTo.javaText != null) + d.text = "\n" + infoCopyTo.javaText; + else if (d.declarator.type != null && d.declarator.type.constructor) { + Parameters params = d.declarator.parameters; + d.text = "\npublic " + shortName + params.list + " { super((Pointer)null); allocate" + params.names + "; }\n" + + d.declarator.type.annotations + "private native void allocate" + params.list + ";\n"; + d.signature = shortName + params.signature; + } else { + d.text = "\n"; + if (cd.namespace != null && !cd.namespace.equals(thisNamespace)) + // Use namespace of the declaration we are copying since it can contain non-qualified types. + // TODO: this annotation won't help if the declaration contains a @Cast or other annotation + // with non-qualified types. Arrange so that auto-generated @Cast in containers are + // always fully-qualified. + d.text += "@Namespace(\""+cd.namespace+"\") "; + d.text += modifiers + d.declarator.type.annotations + context.shorten(d.declarator.type.javaName) + " " + d.declarator.javaName + d.declarator.parameters.list + ";\n"; + } + + declList2.templateMap = cd.templateMap; + declList2.add(d, cd.fullname); + } + } + } + String modifiers = "public static ", constructors = "", inheritedConstructors = ""; boolean implicitConstructor = true, arrayConstructor = false, defaultConstructor = false, longConstructor = false, pointerConstructor = false, abstractClass = info != null && info.purify && !ctx.virtualize, allPureConst = true, haveVariables = false; for (Declaration d : declList2) { + polymorphic |= d.declarator != null && d.declarator.type != null && d.declarator.type.virtual; if (d.declarator != null && d.declarator.type != null && d.declarator.type.using && decl.text != null) { // inheriting constructors defaultConstructor |= d.text.contains("private native void allocate();"); @@ -3637,24 +3741,35 @@ boolean group(Context context, DeclarationList declList) throws ParserException decl.text = declList.rescan(decl.text + casts + "\n"); declList.spacing = null; } - for (Type base2 : baseClasses) { + for (Type base2 : flattenFrom) { Info baseInfo = infoMap.getFirst(base2.cppName); - if (baseInfo != null && baseInfo.flatten && baseInfo.javaText != null) { - String text = baseInfo.javaText; - int start = text.indexOf('{'); - for (int n = 0; n < 2; start++) { - int c = text.charAt(start); - if (c == '\n') { - n++; - } else if (!Character.isWhitespace(c)) { - n = 0; + if (baseInfo != null) { + if (baseInfo.flatten && baseInfo.javaText != null) { + String text = baseInfo.javaText; + int start = text.indexOf('{'); + for (int n = 0; n < 2; start++) { + int c = text.charAt(start); + if (c == '\n') { + n++; + } else if (!Character.isWhitespace(c)) { + n = 0; + } } + int end = text.lastIndexOf('}'); + decl.text += text.substring(start, end).replace(base2.javaName, type.javaName); + decl.custom = true; } - int end = text.lastIndexOf('}'); - decl.text += text.substring(start, end).replace(base2.javaName, type.javaName); - decl.custom = true; } } + if (polymorphic) { + polymorphicClasses.add(type.javaName); + if (!copiedDeclarations.containsKey(type.cppName)) + copiedDeclarations.put(type.cppName, new CopiedDeclarations()); + } + + CopiedDeclarations cp = copiedDeclarations.get(type.cppName); + if (cp != null) cp.addAll(ctx.copiedDeclarations); + for (Declaration d : declList2) { if ((!d.inaccessible || d.declarator != null && d.declarator.type.friend) && (d.declarator == null || d.declarator.type == null @@ -4179,6 +4294,18 @@ void declarations(Context context, DeclarationList declList) throws ParserExcept } } + /** Initialize copiedDeclarations for cppName we need to copy declarations from. */ + private void initializeCopiedDeclarations() { + for (List infoList: infoMap.values()) { + for (Info i: infoList) { + if (i.copyFrom != null) + for (String cf: i.copyFrom) + if (!copiedDeclarations.containsKey(cf)) + copiedDeclarations.put(cf, new CopiedDeclarations()); + } + } + } + void parse(Context context, DeclarationList declList, String[] includePath, @@ -4295,6 +4422,7 @@ public File[] parse(File outputDirectory, String[] classPath, Class cls) throws // fail silently as if the interface wasn't implemented } infoMap.putAll(leafInfoMap); + initializeCopiedDeclarations(); String version = Parser.class.getPackage().getImplementationVersion(); if (version == null) {