Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy declarations between classes #655

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
9 changes: 5 additions & 4 deletions src/main/java/org/bytedeco/javacpp/tools/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ int compile(String[] sourceFilenames, String outputFilename, ClassProperties pro
/**
* Creates and returns the directory where output files should be placed.
* Uses {@link #outputDirectory} as is when available, but falls back
* on the shortest common path to the classes as well as the platform
* on the longest common path to the classes as well as the platform
* specific library path when available, or the platform name itself
* and the user provided extension when not.
*
Expand All @@ -476,13 +476,14 @@ File getOutputPath(Class[] classes, String[] sourcePrefixes) throws IOException
String resourceURL = Loader.findResource(classes[0], resourceName).toString();
String packageURI = resourceURL.substring(0, resourceURL.lastIndexOf('/') + 1);
for (int i = 1; i < classes.length; i++) {
// Use shortest common package name among all classes as default output path
// Use the longest common package name among all classes as default output path
String resourceName2 = '/' + classes[i].getName().replace('.', '/') + ".class";
String resourceURL2 = Loader.findResource(classes[i], resourceName2).toString();
String packageURI2 = resourceURL2.substring(0, resourceURL2.lastIndexOf('/') + 1);

String longest = packageURI2.length() > packageURI.length() ? packageURI2 : packageURI;
String shortest = packageURI2.length() < packageURI.length() ? packageURI2 : packageURI;
String longest, shortest;
if (packageURI2.length() > packageURI.length()) { longest = packageURI2; shortest = packageURI; }
else { longest = packageURI; shortest = packageURI2; }
while (!longest.startsWith(shortest) && shortest.lastIndexOf('/') > 0) {
shortest = shortest.substring(0, shortest.lastIndexOf('/'));
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/bytedeco/javacpp/tools/Context.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ class Context {
TemplateMap templateMap = null;
List<String> usingList = null;
Map<String,String> namespaceMap = null;
CopiedDeclarations copiedDeclarations;

/** Return all likely combinations of namespaces and template arguments for this C++ type */
String[] qualify(String cppName) {
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/org/bytedeco/javacpp/tools/CopiedDeclarations.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.bytedeco.javacpp.tools;

import java.util.ArrayList;

class CopiedDeclarations extends ArrayList<CopiedDeclarations.CopiedDeclaration> {
void add(Declaration decl, String fullname, String modifiers, String namespace) {
add(new CopiedDeclaration(decl, fullname, modifiers, namespace));
}

static class CopiedDeclaration {
final Declaration decl;
final String fullname;
final String modifiers;
final String namespace;

CopiedDeclaration(Declaration decl, String fullname, String modifiers, String namespace) {
this.decl = decl;
this.fullname = fullname;
this.modifiers = modifiers;
this.namespace = namespace;
}
}
}
12 changes: 11 additions & 1 deletion src/main/java/org/bytedeco/javacpp/tools/Declaration.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
*
* @author Samuel Audet
*/
class Declaration {
class Declaration implements Cloneable {
Type type = null;
Declarator declarator = null;
boolean abstractMember = false, constMember = false, inaccessible = false,
Expand All @@ -36,4 +36,14 @@ class Declaration {
public String toString() {
return text;
}

@Override
public Declaration clone() {
try {
// Shallow copy is enough for the current usage
return (Declaration) super.clone();
HGuillemet marked this conversation as resolved.
Show resolved Hide resolved
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
14 changes: 14 additions & 0 deletions src/main/java/org/bytedeco/javacpp/tools/Info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -124,6 +129,14 @@ 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<String> copyFrom = null;
/** If not-null, declarations for these cpp names are copied.
* Set to non-null by InfoMap.normalizeCopy or for polymorphic classes. */
CopiedDeclarations copiedDeclarations = null;
/** Set by Parser when a class has a virtual member function or inherit from a polymorphic class. */
boolean polymorphic = false;
HGuillemet marked this conversation as resolved.
Show resolved Hide resolved


public Info cppNames(String... cppNames) { this.cppNames = cppNames; return this; }
public Info javaNames(String... javaNames) { this.javaNames = javaNames; return this; }
Expand Down Expand Up @@ -161,4 +174,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; }
}
20 changes: 20 additions & 0 deletions src/main/java/org/bytedeco/javacpp/tools/InfoMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.HashSet;

/**
* A {@link Map} containing {@link Info} objects consumed by the {@link Parser}.
Expand Down Expand Up @@ -343,4 +344,23 @@ public InfoMap put(Info info) {
public InfoMap putFirst(Info info) {
return put(0, info);
}

/** Initialize copiedDeclarations for cppName we need to copy declarations from. */
void normalizeCopy() {
HashSet<String> copyFrom = new HashSet<>();
for (List<Info> infoList: values()) {
for (Info i: infoList) {
if (i.copyFrom != null)
copyFrom.addAll(i.copyFrom);
}
}
for (String cppName: copyFrom) {
Info i = getFirst(cppName);
if (i == null) {
i = new Info(cppName);
put(i);
}
i.copiedDeclarations = new CopiedDeclarations();
}
}
}
139 changes: 121 additions & 18 deletions src/main/java/org/bytedeco/javacpp/tools/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -2626,6 +2626,16 @@ 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) {
if (info != null && info.copiedDeclarations != null)
info.copiedDeclarations.add(decl, fullname, modifiers, context.namespace);
if (context.copiedDeclarations != null && !type.constructor)
context.copiedDeclarations.add(decl, fullname, modifiers,context.namespace);
}
}
if (type.virtual && context.virtualize) {
break;
Expand Down Expand Up @@ -3292,17 +3302,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<String> copyFrom = new ArrayList<>();
List<Type> 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;
Expand All @@ -3313,6 +3328,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) {
Expand Down Expand Up @@ -3409,17 +3426,34 @@ 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<Type> it = baseClasses.iterator();
while (it.hasNext()) {
Type next = it.next();
Info nextInfo = infoMap.getFirst(next.cppName);
if (nextInfo == null || !nextInfo.flatten) {
if (nextInfo != null) {
polymorphic |= nextInfo.polymorphic;
if (nextInfo.flatten) {
flattenFrom.add(next);
continue;
} else if (nextInfo.polymorphic && 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) {
Expand Down Expand Up @@ -3501,9 +3535,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 an Info if this class turns out to be
* polymorphic (in case another class will virtually inherit from it) or if the used asked for
* copy using Info.copyFrom. */
ctx.copiedDeclarations = new CopiedDeclarations();

DeclarationList declList2 = new DeclarationList();
if (variables.size() == 0) {
declarations(ctx, declList2);
Expand All @@ -3524,11 +3565,55 @@ 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) {
Info infoCopyFrom = infoMap.getFirst(cf);
if (infoCopyFrom != null && infoCopyFrom.copiedDeclarations != 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 : infoCopyFrom.copiedDeclarations) {
Declaration d = cd.decl.clone();
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 (d.declarator.type != null && d.declarator.type.constructor) {
Parameters params = d.declarator.parameters;
d.text = spacing + "public " + 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 = spacing;
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.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();");
Expand Down Expand Up @@ -3637,24 +3722,41 @@ 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) {
if (info == null) {
info = new Info(cppName);
info.copiedDeclarations = new CopiedDeclarations();
infoMap.put(info);
} else if (info.copiedDeclarations == null) {
info.copiedDeclarations = new CopiedDeclarations();
}
info.polymorphic = true;
}

if (info != null && info.copiedDeclarations != null) {
info.copiedDeclarations.addAll(ctx.copiedDeclarations);
}

for (Declaration d : declList2) {
if ((!d.inaccessible || d.declarator != null && d.declarator.type.friend)
&& (d.declarator == null || d.declarator.type == null
Expand Down Expand Up @@ -4295,6 +4397,7 @@ public File[] parse(File outputDirectory, String[] classPath, Class cls) throws
// fail silently as if the interface wasn't implemented
}
infoMap.putAll(leafInfoMap);
infoMap.normalizeCopy();

String version = Parser.class.getPackage().getImplementationVersion();
if (version == null) {
Expand Down