Skip to content

Commit 3d3aa84

Browse files
authored
Merge pull request #286 from hypfvieh/issue-285
Issue 285
2 parents 847c4fb + 3b9c90e commit 3d3aa84

File tree

9 files changed

+718
-125
lines changed

9 files changed

+718
-125
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ The library will remain open source and MIT licensed and can still be used, fork
133133
- the `AbstractConnection.addSigHandler(DBusMatchRule, SigHandler)` is now `public` and can be used to register arbitrary rules
134134
- the new implementation supports additional MatchRules as defined by DBus Specification (except eavesdrop)
135135
- Extended `EmbeddedDBusDaemon` to properly support MatchRules
136+
- Improved `InterfaceCodeGenerator` to properly create Tuple classes and create empty signal classes as well
136137

137138
##### Changes in 5.1.1 (2025-03-14):
138139
- Added new Helper class `VariantBuilder` to allow creating Variants which contain Maps or Collections without messing with the required DBus type arguments

dbus-java-core/src/main/java/org/freedesktop/dbus/Tuple.java

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,33 @@
22

33
/**
44
* This class should be extended to create Tuples.
5-
*
5+
* <p>
66
* Any such class may be used as the return type for a method
77
* which returns multiple values.
8-
*
8+
* </p><p>
99
* All fields in the Tuple which you wish to be serialized and sent to the
10-
* remote method should be annotated with the org.freedesktop.dbus.Position
10+
* remote method should be annotated with the {@link org.freedesktop.dbus.Position Position}
1111
* annotation, in the order they should appear to DBus.
12+
* </p><p>
13+
* A Tuple should have generic type parameters, otherwise deserialization may fail.
14+
* </p>
15+
* Example class:
16+
* <pre>
17+
* public class MyTuple&lt;A, B&gt; extends Tuple {
18+
* &#64;Position(0)
19+
* private final A firstValue;
20+
* &#64;Position(1)
21+
* private final B secondValue;
22+
*
23+
* // constructor/getter/setter omitted for brevity
24+
* }
25+
* </pre>
26+
* Example usage:
27+
* <pre>
28+
* public SampleDbusInterface extends DBusInterface {
29+
* MyTuple&lt;String, Integer&gt; getMyTuple();
30+
* }
31+
* </pre>
1232
*/
1333
public abstract class Tuple extends Container {
1434
}

dbus-java-core/src/main/java/org/freedesktop/dbus/utils/Util.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -715,4 +715,25 @@ public static <C> Constructor<? extends C> getConstructor(Class<? extends C> _cl
715715
return null;
716716
}
717717
}
718+
719+
/**
720+
* Extracts the class name from a fully qualified class name (FQCN).
721+
* <p>
722+
* If the FQCN is {@code null} or empty, {@code null} will be returned.
723+
* If the FQCN does not contain any dots, the FQCN itself will be returned.
724+
*
725+
* @param _fqcn fully qualified class name
726+
* @return class name or null if input was null/empty
727+
*/
728+
public static String extractClassNameFromFqcn(String _fqcn) {
729+
if (Util.isBlank(_fqcn)) {
730+
return null;
731+
}
732+
int lastDot = _fqcn.lastIndexOf('.');
733+
if (lastDot < 0) {
734+
return _fqcn;
735+
}
736+
return _fqcn.substring(lastDot + 1);
737+
}
738+
718739
}

dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfo.java

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public class ClassBuilderInfo {
3535
/** Constructors for this class. */
3636
private final List<ClassConstructor> constructors = new ArrayList<>();
3737

38+
private final List<String> generics = new ArrayList<>();
39+
3840
/** Prefix to prepend to method/constructor arguments. */
3941
private final String argumentPrefix;
4042

@@ -134,6 +136,10 @@ public List<ClassConstructor> getConstructors() {
134136
return constructors;
135137
}
136138

139+
public List<String> getGenerics() {
140+
return generics;
141+
}
142+
137143
/**
138144
* Create the Java source for the class information provided.
139145
*
@@ -191,14 +197,17 @@ private List<String> createClassFileContent(boolean _staticClass, Set<String> _o
191197

192198
String bgn = classIndent + "public " + (_staticClass ? "static " : "") + (getClassType() == ClassType.INTERFACE ? "interface" : "class");
193199
bgn += " " + getClassName();
200+
if (!getGenerics().isEmpty()) {
201+
bgn += "<" + String.join(", ", getGenerics()) + ">";
202+
}
194203
if (getExtendClass() != null) {
195204
Set<String> lImports = getImportsForType(getExtendClass());
196205
getImports().addAll(lImports);
197206
allImports.addAll(lImports);
198207
bgn += " extends " + getSimpleTypeClasses(getExtendClass());
199208
}
200209
if (!getImplementedInterfaces().isEmpty()) {
201-
bgn += " implements " + getImplementedInterfaces().stream().map(ClassBuilderInfo::getClassName).collect(Collectors.joining(", "));
210+
bgn += " implements " + getImplementedInterfaces().stream().map(Util::extractClassNameFromFqcn).collect(Collectors.joining(", "));
202211
// add classes import if implements-arguments are not a java.lang. classes
203212
getImports().addAll(getImplementedInterfaces().stream().filter(s -> !s.startsWith("java.lang.")).toList());
204213
}
@@ -310,24 +319,6 @@ public String toString() {
310319
+ extendClass + "]";
311320
}
312321

313-
/**
314-
* Extract the class name from a given FQCN (fully qualified classname).
315-
*
316-
* @param _fqcn fqcn to analyze
317-
* @return classname, null if input was null
318-
*/
319-
static String getClassName(String _fqcn) {
320-
if (_fqcn == null) {
321-
return null;
322-
}
323-
324-
String clzzName = _fqcn;
325-
if (clzzName.contains(".")) {
326-
clzzName = clzzName.substring(clzzName.lastIndexOf('.') + 1);
327-
}
328-
return clzzName;
329-
}
330-
331322
/**
332323
* Simplify class names in the type. Please go to unit tests for usage examples.
333324
*
@@ -343,7 +334,7 @@ static String getSimpleTypeClasses(String _type) {
343334
Matcher matcher = compile.matcher(_type);
344335
while (matcher.find()) {
345336
String match = matcher.group();
346-
matcher.appendReplacement(sb, getClassName(match));
337+
matcher.appendReplacement(sb, Util.extractClassNameFromFqcn(match));
347338
}
348339
matcher.appendTail(sb);
349340
return sb.toString();
@@ -498,7 +489,7 @@ public static class MemberOrArgument {
498489
/** Name of member/field. */
499490
private final String name;
500491
/** Type of member/field (e.g. String, int...). */
501-
private final String type;
492+
private String type;
502493
/** True to force this member to be final, false otherwise. */
503494
private final boolean finalArg;
504495
/** List of classes/types or placeholders put into diamond operators to use as generics. */
@@ -529,6 +520,10 @@ public String getType() {
529520
return type;
530521
}
531522

523+
public void setType(String _type) {
524+
type = _type;
525+
}
526+
532527
public boolean isFinalArg() {
533528
return finalArg;
534529
}

dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGenerator.java

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ private Map<File, String> extractAll(Element _ife) throws IOException, DBusExcep
186186
switch (element.getTagName().toLowerCase()) {
187187
case "method" -> additionalClasses.addAll(extractMethods(element, interfaceClass));
188188
case "property" -> additionalClasses.addAll(extractProperties(element, interfaceClass));
189-
case "signal" -> additionalClasses.addAll(extractSignals(element, interfaceClass));
189+
case "signal" -> extractSignals(element, interfaceClass);
190190
}
191191
}
192192

@@ -203,18 +203,11 @@ private Map<File, String> extractAll(Element _ife) throws IOException, DBusExcep
203203
*
204204
* @param _signalElement signal xml element
205205
* @param _clzBldr {@link ClassBuilderInfo} object
206-
* @return List of {@link ClassBuilderInfo} which have been created (maybe empty, never null)
207206
*
208207
* @throws IOException on IO Error
209208
* @throws DBusException on DBus Error
210209
*/
211-
private List<ClassBuilderInfo> extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) throws IOException, DBusException {
212-
List<ClassBuilderInfo> additionalClasses = new ArrayList<>();
213-
214-
if (!_signalElement.hasChildNodes()) { // signal without any input/output?!
215-
logger.warn("Signal without any input/output arguments. These are not supported yet, please open a ticket at github!");
216-
return additionalClasses;
217-
}
210+
private void extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) throws IOException, DBusException {
218211

219212
String className = _signalElement.getAttribute("name");
220213
if (className.contains(".")) {
@@ -229,42 +222,42 @@ private List<ClassBuilderInfo> extractSignals(Element _signalElement, ClassBuild
229222
innerClass.setClassName(className);
230223

231224
_clzBldr.getInnerClasses().add(innerClass);
225+
List<MemberOrArgument> argsList = new ArrayList<>();
226+
227+
if (!_signalElement.hasChildNodes()) { // signal without any input/output?!
228+
logger.info("Signal without any input/output arguments. Creating empty signal class: {}", innerClass.getFqcn());
229+
} else {
230+
List<Element> signalArgs = XmlUtil.convertToElementList(XmlUtil.applyXpathExpressionToDocument("arg", _signalElement));
232231

233-
List<Element> signalArgs = XmlUtil.convertToElementList(XmlUtil.applyXpathExpressionToDocument("arg", _signalElement));
232+
Map<String, String> args = new LinkedHashMap<>();
234233

235-
Map<String, String> args = new LinkedHashMap<>();
234+
int unknownArgCnt = 0;
235+
for (Element argElm : signalArgs) {
236+
String argType = TypeConverter.getJavaTypeFromDBusType(argElm.getAttribute("type"), _clzBldr.getImports());
237+
String argName = Util.snakeToCamelCase(argElm.getAttribute("name"));
238+
if (Util.isBlank(argName)) {
239+
argName = "arg" + unknownArgCnt;
240+
unknownArgCnt++;
241+
}
242+
args.put(argName, TypeConverter.getProperJavaClass(argType, _clzBldr.getImports()));
243+
}
236244

237-
int unknownArgCnt = 0;
238-
for (Element argElm : signalArgs) {
239-
String argType = TypeConverter.getJavaTypeFromDBusType(argElm.getAttribute("type"), _clzBldr.getImports());
240-
String argName = Util.snakeToCamelCase(argElm.getAttribute("name"));
241-
if (Util.isBlank(argName)) {
242-
argName = "arg" + unknownArgCnt;
243-
unknownArgCnt++;
245+
for (Entry<String, String> argEntry : args.entrySet()) {
246+
innerClass.getMembers().add(new MemberOrArgument(argEntry.getKey(), argEntry.getValue(), true));
247+
argsList.add(new MemberOrArgument(argEntry.getKey(), argEntry.getValue(), false));
244248
}
245-
args.put(argName, TypeConverter.getProperJavaClass(argType, _clzBldr.getImports()));
246-
}
247249

248-
for (Entry<String, String> argEntry : args.entrySet()) {
249-
innerClass.getMembers().add(new MemberOrArgument(argEntry.getKey(), argEntry.getValue(), true));
250250
}
251251

252252
ClassConstructor classConstructor = new ClassBuilderInfo.ClassConstructor();
253253

254-
List<MemberOrArgument> argsList = new ArrayList<>();
255-
for (Entry<String, String> e : args.entrySet()) {
256-
argsList.add(new MemberOrArgument("_" + e.getKey(), e.getValue(), false));
257-
}
258-
259254
classConstructor.getArguments().addAll(argsList);
260255
classConstructor.getThrowArguments().add(DBusException.class.getSimpleName());
261256

262-
classConstructor.getSuperArguments().add(new MemberOrArgument("_path", "String", false));
257+
classConstructor.getSuperArguments().add(new MemberOrArgument("path", "String", false));
263258
classConstructor.getSuperArguments().addAll(argsList);
264259

265260
innerClass.getConstructors().add(classConstructor);
266-
267-
return additionalClasses;
268261
}
269262

270263
/**
@@ -327,7 +320,22 @@ private List<ClassBuilderInfo> extractMethods(Element _methodElement, ClassBuild
327320
String resultType;
328321
if (outputArgs.size() > 1) { // multi-value return
329322
logger.debug("Found method with multiple return values: {}", methodElementName);
330-
resultType = createTuple(outputArgs, methodElementName + "Tuple", _clzBldr, additionalClasses, dbusOutputArgTypes);
323+
List<String> genericTypes = new ArrayList<>();
324+
325+
resultType = createTuple(outputArgs, methodElementName + "Tuple", _clzBldr, additionalClasses, dbusOutputArgTypes, genericTypes);
326+
327+
genericTypes.stream()
328+
.flatMap(e -> ClassBuilderInfo.getImportsForType(e).stream())
329+
.forEach(_clzBldr.getImports()::add);
330+
331+
_clzBldr.getImports().add(resultType);
332+
333+
resultType = Util.extractClassNameFromFqcn(resultType);
334+
List<String> returnGenerics = genericTypes.stream()
335+
.map(TypeConverter::convertJavaPrimitiveToBoxed)
336+
.map(ClassBuilderInfo::getSimpleTypeClasses)
337+
.toList();
338+
resultType += "<" + String.join(", ", returnGenerics) + ">";
331339
} else {
332340
logger.debug("Found method with arguments: {}({})", methodElementName, inputArgs);
333341
resultType = outputArgs.isEmpty() ? "void" : outputArgs.get(0).getFullType(new HashSet<>());
@@ -401,7 +409,7 @@ private List<ClassBuilderInfo> extractProperties(Element _propertyElement, Class
401409
ClassBuilderInfo propertyTypeRef = null;
402410
String origType = null;
403411
if (!isComplex) {
404-
clzzName = ClassBuilderInfo.getClassName(type);
412+
clzzName = Util.extractClassNameFromFqcn(type);
405413
} else {
406414
origType = type;
407415
type = TypeRef.class.getName() + "<" + type + ">";
@@ -464,7 +472,8 @@ private List<ClassBuilderInfo> extractProperties(Element _propertyElement, Class
464472
* @param _dbusOutputArgTypes Dbus argument names and data types
465473
* @return FQCN of the newly created tuple based class
466474
*/
467-
private String createTuple(List<MemberOrArgument> _outputArgs, String _className, ClassBuilderInfo _parentClzBldr, List<ClassBuilderInfo> _additionalClasses, List<String> _dbusOutputArgTypes) {
475+
private String createTuple(List<MemberOrArgument> _outputArgs, String _className,
476+
ClassBuilderInfo _parentClzBldr, List<ClassBuilderInfo> _additionalClasses, List<String> _dbusOutputArgTypes, List<String> _genericTypes) {
468477
if (_outputArgs == null || _outputArgs.isEmpty() || _additionalClasses == null) {
469478
return null;
470479
}
@@ -478,29 +487,48 @@ private String createTuple(List<MemberOrArgument> _outputArgs, String _className
478487
info.getImports().add(Position.class.getName());
479488
}
480489

481-
ArrayList<MemberOrArgument> cnstrctArgs = new ArrayList<>();
490+
List<MemberOrArgument> cnstrctArgs = new ArrayList<>();
491+
Map<String, String> genericTypes = new LinkedHashMap<>();
492+
482493
int position = 0;
483494
for (MemberOrArgument entry : _outputArgs) {
484-
entry.getAnnotations().add("@Position(" + position++ + ")");
485-
cnstrctArgs.add(new MemberOrArgument(entry.getName(), entry.getType()));
486-
}
495+
String genericName = findNextGenericName(genericTypes.keySet());
487496

488-
for (String outputType : _dbusOutputArgTypes) {
489-
for (String part : outputType.replace(" ", "").replace(">", "").split("<|,")) {
490-
info.getImports().add(part);
491-
}
497+
genericTypes.put(genericName, entry.getType());
498+
entry.getAnnotations().add("@Position(" + position++ + ")");
499+
entry.setType(genericName);
500+
cnstrctArgs.add(new MemberOrArgument(entry.getName(), genericName));
492501
}
493502

494503
ClassConstructor cnstrct = new ClassConstructor();
495504
cnstrct.getArguments().addAll(cnstrctArgs);
496505

497506
info.getConstructors().add(cnstrct);
498507
info.getMembers().addAll(_outputArgs);
508+
info.getGenerics().addAll(genericTypes.keySet());
509+
499510
_additionalClasses.add(info);
511+
_genericTypes.addAll(genericTypes.values());
500512

501513
return info.getFqcn();
502514
}
503515

516+
/**
517+
* Tries to find next available generic name for a Tuple class.
518+
* @param _used Set of already used names
519+
* @return next available name
520+
* @throws IllegalStateException if no name could be found
521+
*/
522+
static String findNextGenericName(Set<String> _used) {
523+
for (char c = 'A'; c <= 'Z'; c++) {
524+
String name = String.valueOf(c);
525+
if (!_used.contains(name)) {
526+
return name;
527+
}
528+
}
529+
throw new IllegalStateException("Unable to find a generic name for Tuple class");
530+
}
531+
504532
/**
505533
* Creates a class for a DBus Struct-Object.
506534
*
@@ -514,14 +542,8 @@ private String createTuple(List<MemberOrArgument> _outputArgs, String _className
514542
*/
515543
private String buildStructClass(String _dbusTypeStr, String _structName, ClassBuilderInfo _packageName, List<ClassBuilderInfo> _structClasses) throws DBusException {
516544
String structFqcn = _packageName.getPackageName() + "." + Util.upperCaseFirstChar(_structName);
517-
String structName = _structName;
518-
if (generatedStructClassNames.contains(structFqcn)) {
519-
while (generatedStructClassNames.contains(structFqcn)) {
520-
structFqcn += "Struct";
521-
structName += "Struct";
522-
}
523-
}
524-
String structClassName = new StructTreeBuilder(argumentPrefix).buildStructClasses(_dbusTypeStr, structName, _packageName, _structClasses);
545+
String structClassName = new StructTreeBuilder(argumentPrefix, generatedStructClassNames)
546+
.buildStructClasses(_dbusTypeStr, structFqcn, _packageName, _structClasses);
525547
generatedStructClassNames.add(structFqcn);
526548
return structClassName;
527549
}

0 commit comments

Comments
 (0)