diff --git a/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/Util.java b/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/Util.java index 7081bb276..a32478119 100644 --- a/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/Util.java +++ b/dbus-java-core/src/main/java/org/freedesktop/dbus/utils/Util.java @@ -163,18 +163,33 @@ public static String upperCaseFirstChar(String _str) { * @return camel case string or input if nothing todo. Returns null if input was null. */ public static String snakeToCamelCase(String _input) { + return toCamelCase("_", _input); + } + + /** + * Converts a kabab-case-string to camel case string. + *
+ * Eg. this-is-kebab-case → thisIsSnakeCase + * @param _input string + * @return camel case string or input if nothing todo. Returns null if input was null. + */ + public static String kebabToCamelCase(String _input) { + return toCamelCase("-", _input); + } + + private static String toCamelCase(String _delimiter, String _input) { if (isBlank(_input)) { return _input; } - Pattern compile = Pattern.compile("_[a-zA-Z]"); + Pattern compile = Pattern.compile(Pattern.quote(_delimiter) + "[a-zA-Z0-9]"); Matcher matcher = compile.matcher(_input); String result = _input; while (matcher.find()) { String match = matcher.group(); - String replacement = match.replace("_", ""); + String replacement = match.replace(_delimiter, ""); replacement = replacement.toUpperCase(); result = result.replaceFirst(match, replacement); diff --git a/dbus-java-tests/src/test/java/org/freedesktop/dbus/utils/UtilTest.java b/dbus-java-tests/src/test/java/org/freedesktop/dbus/utils/UtilTest.java index 5b3bb88db..f40d634d0 100644 --- a/dbus-java-tests/src/test/java/org/freedesktop/dbus/utils/UtilTest.java +++ b/dbus-java-tests/src/test/java/org/freedesktop/dbus/utils/UtilTest.java @@ -21,4 +21,15 @@ void testRequireMinimum(String _name, int _minVal, int _testVal, boolean _throws assertEquals(_testVal, result); } } + + @ParameterizedTest(name = "{index}: {0}") + @CsvSource({ + "No Snake,This is no snake,This is no snake", + "Snake,This_is_a_snake,ThisIsASnake", + "Partial Snake,This_is_partial snake,ThisIsPartial snake", + "Snake with numbers,This_is_0_8_15_snake,ThisIs0815Snake", + }) + void testSnakeToCamelCase(String _name, String _input, String _expected) { + assertEquals(_expected, Util.snakeToCamelCase(_input)); + } } diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfo.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfo.java index e0720fe70..2b4bd48a8 100644 --- a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfo.java +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfo.java @@ -1,9 +1,16 @@ package org.freedesktop.dbus.utils.generator; +import org.freedesktop.dbus.annotations.DBusBoundProperty; import org.freedesktop.dbus.annotations.DBusInterfaceName; +import org.freedesktop.dbus.annotations.DBusMemberName; +import org.freedesktop.dbus.messages.Message; import org.freedesktop.dbus.utils.Util; import org.freedesktop.dbus.utils.bin.IdentifierMangler; +import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.AnnotationInfo.AnnotArgs; +import org.slf4j.LoggerFactory; +import java.beans.IntrospectionException; +import java.beans.Introspector; import java.io.File; import java.lang.annotation.Annotation; import java.util.*; @@ -19,7 +26,9 @@ */ public class ClassBuilderInfo { - private static final String DEFAULT_INDENT = " "; + private static final Set RESERVED_METHOD_NAMES = getReservedMethods(Message.class); + + private static final String DEFAULT_INDENT = " "; /** Imported files for this class. */ private final Set imports = new TreeSet<>(); /** Annotations of this class. */ @@ -159,17 +168,19 @@ public String createClassFileContent() { * @return */ private List createClassFileContent(boolean _staticClass, Set _otherImports) { - List content = new ArrayList<>(); final String classIndent = _staticClass ? DEFAULT_INDENT : ""; final String memberIndent = _staticClass ? DEFAULT_INDENT.repeat(2) : DEFAULT_INDENT; Set allImports = new TreeSet<>(); allImports.addAll(getImports()); + if (_otherImports != null) { allImports.addAll(_otherImports); } + List content = new ArrayList<>(); + if (!_staticClass) { content.add("package " + getPackageName() + ";"); content.add(""); @@ -188,6 +199,8 @@ private List createClassFileContent(boolean _staticClass, Set _o for (AnnotationInfo annotation : annotations) { allImports.add(annotation.getAnnotationClass().getName()); + allImports.addAll(annotation.getAdditionalImports().stream().map(Class::getName).toList()); + String annotationCode = classIndent + "@" + annotation.getAnnotationClass().getSimpleName(); if (annotation.getAnnotationParams() != null) { annotationCode += "(" + annotation.getAnnotationParams() + ")"; @@ -222,7 +235,11 @@ private List createClassFileContent(boolean _staticClass, Set _o // add member fields for (MemberOrArgument member : members) { if (!member.getAnnotations().isEmpty()) { - content.addAll(member.getAnnotations().stream().map(l -> memberIndent + l).toList()); + member.getAnnotations().stream().forEach(l -> { + content.add(memberIndent + l.getAnnotationString()); + allImports.addAll(l.getAdditionalImports().stream().map(Class::getName).toList()); + allImports.add(l.getAnnotationClass().getName()); + }); } content.add(memberIndent + "private " + member.asOneLineString(allImports, "", false) + ";"); } @@ -265,6 +282,9 @@ private List createClassFileContent(boolean _staticClass, Set _o .filter(l -> l.contains(".")) // no dots in name means this is only a class name so we are in same package and don't need to import .map(l -> "import " + l + ";") .toList()); + } else { + // add the collected additional imports to the provided set when creating inner class + _otherImports.addAll(allImports); } return content; @@ -367,6 +387,28 @@ static Set getImportsForType(String _type) { return imports; } + static Set getReservedMethods(Class _class) { + try { + return Arrays.stream(Introspector.getBeanInfo(_class).getMethodDescriptors()) + .map(e -> e.getMethod().getName()) + .collect(Collectors.toSet()); + } catch (IntrospectionException _ex) { + LoggerFactory.getLogger(ClassBuilderInfo.class).error("Could not extract method names from {}", _class, _ex); + return Set.of(); + } + } + + /** + * Check if the provided method name classes with any method name found in {@link #RESERVED_METHOD_NAMES}. + * Will check the given method name with usual Java method prefixes (get/is/set) as well. + * @param _methodName method name + * @return true if reserved + */ + static boolean isReservedMethodName(String _methodName) { + return RESERVED_METHOD_NAMES.contains(_methodName) || RESERVED_METHOD_NAMES.contains("set" + _methodName) + || RESERVED_METHOD_NAMES.contains("get" + _methodName) || RESERVED_METHOD_NAMES.contains("is" + _methodName); + } + /** * Contains information about annotation to place on classes, members or methods. * @@ -374,23 +416,124 @@ static Set getImportsForType(String _type) { * @since v3.2.1 - 2019-11-13 */ public static class AnnotationInfo { + /** Annotation class. */ private final Class annotationClass; - /** Annotation params (e.g. value = "foo", key = "bar"). */ - private final String annotationParams; + /** Map of parameters for the annotation (should be ordered). */ + private final Map annotationParams = new LinkedHashMap<>(); - public AnnotationInfo(Class _annotationClass, String _annotationParams) { + private final Set> additionalImports = new LinkedHashSet<>(); + + public AnnotationInfo(Class _annotationClass, AnnotArgs _annotationParams) { annotationClass = _annotationClass; - annotationParams = _annotationParams; + if (_annotationParams != null) { + _annotationParams.args.forEach(e -> { + annotationParams.put(e.key(), e.value()); + if (e.value() != null && !e.value().getClass().getPackage().getName().startsWith("java.lang")) { + additionalImports.add(e.value().getClass()); + } + }); + } } public Class getAnnotationClass() { return annotationClass; } - public String getAnnotationParams() { + public Map getAnnotationParams() { return annotationParams; } + + public Set> getAdditionalImports() { + return additionalImports; + } + + public String getAnnotationString() { + StringBuilder sb = new StringBuilder(); + sb.append("@").append(getAnnotationClass().getSimpleName()); + + if (!getAnnotationParams().isEmpty()) { + sb.append("("); + + if (getAnnotationParams().size() == 1 && "value".equals(getAnnotationParams().keySet().iterator().next())) { + sb.append(handleArg(getAnnotationParams().values().iterator().next())); + } else { + getAnnotationParams().forEach((k, v) -> { + sb.append(k).append(" = "); + sb.append(handleArg(v)); + }); + } + + sb.append(")"); + } + + return sb.toString(); + } + + @Override + public String toString() { + return "AnnotationInfo [annotationClass=" + annotationClass + ", annotationParams=" + annotationParams + ", additionalImports=" + additionalImports + "]"; + } + + private String handleArg(Object _value) { + if (_value instanceof String s) { + return "\"" + s + "\""; + } else { + return String.valueOf(_value); + } + } + + public static final class AnnotArgs { + private final Set args = new LinkedHashSet<>(); + + private AnnotArgs() { + + } + + public AnnotArgs add(String _key, Object _val) { + Objects.requireNonNull(_key); + Objects.requireNonNull(_val); + + args.add(new AnnotArg(_key, _val)); + return this; + } + + /** + * Shortcut for {@code #add("value", _val)}. + * @param _val value to set for "value" option + * @return this + */ + public AnnotArgs add(Object _val) { + return this.add("value", _val); + } + + public static AnnotArgs create() { + return new AnnotArgs(); + } + + record AnnotArg(String key, Object value) { + + @Override + public int hashCode() { + return Objects.hash(key); + } + + @Override + public boolean equals(Object _obj) { + if (this == _obj) { + return true; + } + if (_obj == null) { + return false; + } + if (getClass() != _obj.getClass()) { + return false; + } + AnnotArg other = (AnnotArg) _obj; + return Objects.equals(key, other.key); + } + } + } } /** @@ -402,24 +545,31 @@ public String getAnnotationParams() { public static class ClassMethod { private static final String METHOD_TEMPL = """ - %s%s %s(%s); + %s%s %s%s(%s); """; /** Name of this method. */ private final String name; /** Return value of the method. */ private final String returnType; + /** Prefix for method name (e.g. get or is), {@code null} if not needed. */ + private final String methodPrefix; /** True if method should be final, false otherwise. */ private final boolean finalMethod; /** Arguments for this method, key is argument name, value is argument type. */ - private final List arguments = new ArrayList<>(); + private final List arguments = new ArrayList<>(); /** List of annotations for this method. */ - private final List annotations = new ArrayList<>(); + private final List annotations = new ArrayList<>(); public ClassMethod(String _name, String _returnType, boolean _finalMethod) { + this(_name, _returnType, null, _finalMethod); + } + + public ClassMethod(String _name, String _returnType, String _methodPrefix, boolean _finalMethod) { name = _name; returnType = _returnType; finalMethod = _finalMethod; + methodPrefix = _methodPrefix; } public String getName() { @@ -430,6 +580,10 @@ public String getReturnType() { return returnType; } + public String getMethodPrefix() { + return methodPrefix; + } + public boolean isFinalMethod() { return finalMethod; } @@ -438,27 +592,53 @@ public List getArguments() { return arguments; } - public List getAnnotations() { + public List getAnnotations() { return annotations; } public List generateCode(boolean _isInterface, String _argumentPrefix, String _indent, Set _allImports) { List result = new ArrayList<>(); - if (!getAnnotations().isEmpty()) { - result.addAll(getAnnotations().stream().map(a -> _indent + a).toList()); + + String methodName = Util.kebabToCamelCase(Util.snakeToCamelCase(getName())); + + List currentAnnotations = new ArrayList<>(getAnnotations()); + + // java method name differs from bus method name -> add annotation to mitigate + if (!getName().equals(methodName)) { + if (currentAnnotations.stream().anyMatch(e -> e.getAnnotationClass() == DBusBoundProperty.class)) { + currentAnnotations.stream().filter(e -> e.getAnnotationClass() == DBusBoundProperty.class) + .forEach(e -> e.getAnnotationParams().put("name", getName())); + } else { + currentAnnotations.add(new AnnotationInfo(DBusMemberName.class, AnnotArgs.create().add(getName()))); + } + } + + if (!currentAnnotations.isEmpty()) { + result.addAll(currentAnnotations.stream().map(a -> _indent + a.getAnnotationString()).toList()); } String publicModifier = !_isInterface ? "public " : ""; String mthReturnType = getReturnType() == null ? "void" : TypeConverter.getProperJavaClass(getReturnType(), _allImports); String args = ""; + if (!getArguments().isEmpty()) { args += getArguments().stream() .map(e -> e.asOneLineString(_allImports, _argumentPrefix, true)) .collect(Collectors.joining(", ")); } - METHOD_TEMPL.formatted(publicModifier, mthReturnType, getName(), args) + String prefix = ""; + if (!Util.isBlank(getMethodPrefix())) { + methodName = Util.upperCaseFirstChar(methodName); + prefix = getMethodPrefix(); + } + + if (isReservedMethodName(methodName) || isReservedMethodName(prefix + methodName)) { + methodName += methodName + "FromBus"; + } + + METHOD_TEMPL.formatted(publicModifier, mthReturnType, prefix, methodName, args) .lines().map(l -> _indent + l).forEach(result::add); return result; @@ -474,6 +654,8 @@ public List generateCode(boolean _isInterface, String _argumentPrefix, S */ public static class MemberOrArgument { + private static final String GETTER_SETTER_ANNOTATION = "@%s(\"%s\")"; + private static final String GETTER_TEMPL = """ public %s get%s() { %sreturn %s; @@ -495,7 +677,7 @@ public static class MemberOrArgument { /** List of classes/types or placeholders put into diamond operators to use as generics. */ private final List generics = new ArrayList<>(); /** List of annotations for this member. */ - private final List annotations = new ArrayList<>(); + private final List annotations = new ArrayList<>(); public MemberOrArgument(String _name, String _type, boolean _finalMember) { // repair reserved words by adding 'Param' as appendix, and when start with _ too @@ -508,7 +690,7 @@ public MemberOrArgument(String _name, String _type) { this(_name, _type, false); } - public List getAnnotations() { + public List getAnnotations() { return annotations; } @@ -553,7 +735,7 @@ public String asOneLineString(Set _allImports, String _prefix, boolean _ } if (_includeAnnotations && !getAnnotations().isEmpty()) { - sb.append(String.join(" ", getAnnotations())) + sb.append(String.join(" ", getAnnotations().stream().map(e -> e.getAnnotationString()).toList())) .append(" "); } @@ -574,14 +756,33 @@ public List generateCode(String _indent, String _prefix, Set _al memberType += "<" + getGenerics().stream().map(c -> TypeConverter.convertJavaType(c, false)).collect(Collectors.joining(", ")) + ">"; } - String getterSetterName = Util.snakeToCamelCase(Util.upperCaseFirstChar(getName())); + String getterSetterName = Util.kebabToCamelCase(Util.snakeToCamelCase(Util.upperCaseFirstChar(getName()))); + + String getterAnnotation = ""; + + if (isReservedMethodName(getterSetterName)) { + getterAnnotation = GETTER_SETTER_ANNOTATION.formatted(DBusMemberName.class.getSimpleName(), getterSetterName); + + if (isReservedMethodName(getterSetterName)) { + _allImports.add(DBusMemberName.class.getName()); + } + + getterSetterName += "FromBus"; + } + if (!isFinalArg()) { + if (!Util.isBlank(getterAnnotation)) { + result.add(_indent + getterAnnotation); + } SETTER_TEMPL.formatted(getterSetterName, memberType, maybePrefix("arg", _prefix), DEFAULT_INDENT, getName(), maybePrefix("arg", _prefix)) .lines().map(l -> _indent + l).forEach(result::add); } addEmptyLineIfNeeded(result); + if (!Util.isBlank(getterAnnotation)) { + result.add(_indent + getterAnnotation); + } GETTER_TEMPL.formatted(memberType, getterSetterName, DEFAULT_INDENT, getName()) .lines().map(l -> _indent + l).forEach(result::add); @@ -657,13 +858,14 @@ public List generatedCode(String _indent, String _className, String _arg if (!getSuperArguments().isEmpty()) { assignments = " ".repeat(_indent.length() / 2) + "super(" + getSuperArguments().stream() .map(e -> maybePrefix(e.getName(), _argumentPrefix)) - .collect(Collectors.joining(", ")) + ");"; + .collect(Collectors.joining(", ")) + ");" + System.lineSeparator(); } if (!getArguments().isEmpty()) { List assigns = new ArrayList<>(); + String innerIndent = " ".repeat(_indent.length() / 2); for (MemberOrArgument e : getArguments()) { - assigns.add(_indent + "this." + e.getName() + " = " + maybePrefix(e.getName(), _argumentPrefix) + ";"); + assigns.add(innerIndent + "this." + e.getName() + " = " + maybePrefix(e.getName(), _argumentPrefix) + ";"); } assignments += String.join(System.lineSeparator(), assigns); } diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGenerator.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGenerator.java index 25ea2a3c5..8d300bd40 100644 --- a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGenerator.java +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGenerator.java @@ -19,6 +19,7 @@ import org.freedesktop.dbus.utils.Util; import org.freedesktop.dbus.utils.XmlUtil; import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.*; +import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.AnnotationInfo.AnnotArgs; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -47,6 +48,8 @@ */ public class InterfaceCodeGenerator { + private static final String STRUCT_CLASS_SUFFIX = "Struct"; + private final DocumentBuilderFactory docFac = DocumentBuilderFactory.newInstance(); private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -186,7 +189,8 @@ private Map extractAll(Element _ife) throws IOException, DBusExcep interfaceClass.setClassName(className); if (forcePackageName != null) { interfaceClass.getAnnotations().add(new AnnotationInfo(DBusInterfaceName.class, - "\"" + originalPackageName + "." + className + "\"")); + AnnotArgs.create().add(originalPackageName + "." + className) + )); } interfaceClass.setExtendClass(DBusInterface.class.getName()); @@ -197,7 +201,7 @@ private Map extractAll(Element _ife) throws IOException, DBusExcep switch (element.getTagName().toLowerCase()) { case "method" -> additionalClasses.addAll(extractMethods(element, interfaceClass)); case "property" -> additionalClasses.addAll(extractProperties(element, interfaceClass)); - case "signal" -> extractSignals(element, interfaceClass); + case "signal" -> additionalClasses.addAll(extractSignals(element, interfaceClass)); } } @@ -214,11 +218,12 @@ private Map extractAll(Element _ife) throws IOException, DBusExcep * * @param _signalElement signal xml element * @param _clzBldr {@link ClassBuilderInfo} object + * @return list containing additionally created class or empty list * * @throws IOException on IO Error * @throws DBusException on DBus Error */ - private void extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) throws IOException, DBusException { + private List extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) throws IOException, DBusException { String className = _signalElement.getAttribute("name"); if (className.contains(".")) { @@ -234,6 +239,7 @@ private void extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) t _clzBldr.getInnerClasses().add(innerClass); List argsList = new ArrayList<>(); + List additionalClasses = new ArrayList<>(); if (!_signalElement.hasChildNodes()) { // signal without any input/output?! logger.info("Signal without any input/output arguments. Creating empty signal class: {}", innerClass.getFqcn()); @@ -244,8 +250,11 @@ private void extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) t int unknownArgCnt = 0; for (Element argElm : signalArgs) { - String argType = TypeConverter.getJavaTypeFromDBusType(argElm.getAttribute("type"), _clzBldr.getImports()); + // _clzBldr, additionalClasses, methodElementName, argElm, argName String argName = Util.snakeToCamelCase(argElm.getAttribute("name")); + + String argType = extractOrCreateArgType(_clzBldr, additionalClasses, className, argElm.getAttribute("type"), argName); + TypeConverter.getJavaTypeFromDBusType(argElm.getAttribute("type"), _clzBldr.getImports()); if (Util.isBlank(argName)) { argName = "arg" + unknownArgCnt; unknownArgCnt++; @@ -269,6 +278,8 @@ private void extractSignals(Element _signalElement, ClassBuilderInfo _clzBldr) t classConstructor.getSuperArguments().addAll(argsList); innerClass.getConstructors().add(classConstructor); + + return additionalClasses; } /** @@ -300,13 +311,7 @@ private List extractMethods(Element _methodElement, ClassBuild String argType; String argName = argElm.getAttribute("name"); - if (argElm.getAttribute("type").contains("(")) { // this argument requires some sort of struct - String structPart = argElm.getAttribute("type").replaceAll("(\\(.+\\))", "$1"); - String paramName = Util.defaultString(Util.upperCaseFirstChar(Util.snakeToCamelCase(argName)), ""); - argType = buildStructClass(structPart, methodElementName + paramName + "Struct", _clzBldr, additionalClasses); - } else { - argType = TypeConverter.getJavaTypeFromDBusType(argElm.getAttribute("type"), _clzBldr.getImports()); - } + argType = extractOrCreateArgType(_clzBldr, additionalClasses, methodElementName, argElm.getAttribute("type"), argName); if (Util.isBlank(argName)) { argName = "arg" + unknownArgNameCnt; @@ -331,7 +336,7 @@ private List extractMethods(Element _methodElement, ClassBuild List genericTypes = new ArrayList<>(); if (avoidUsingTuple) { - resultType = buildStructClass(dbusSignatures, methodElementName + "Struct", _clzBldr, additionalClasses); + resultType = buildStructClass(dbusSignatures, methodElementName + STRUCT_CLASS_SUFFIX, _clzBldr, additionalClasses); } else { resultType = createTuple(outputArgs, methodElementName + "Tuple", _clzBldr, additionalClasses, genericTypes); } @@ -370,6 +375,24 @@ private List extractMethods(Element _methodElement, ClassBuild } + private String extractOrCreateArgType(ClassBuilderInfo _clzBldr, List _additionalClasses, + String _methodElementName, String _argTypeStringFromElement, String _argName) throws DBusException { + String argType; + if (_argTypeStringFromElement.contains("(")) { // this argument requires some sort of struct + String structPart = _argTypeStringFromElement.replaceAll("(\\(.+\\))", "$1"); + String paramName = Util.defaultString(Util.upperCaseFirstChar(Util.snakeToCamelCase(_argName)), ""); + String parentType = buildStructClass(structPart, _methodElementName + paramName + STRUCT_CLASS_SUFFIX, _clzBldr, _additionalClasses); + if (parentType != null) { + argType = parentType; + } else { + argType = null; + } + } else { + argType = TypeConverter.getJavaTypeFromDBusType(_argTypeStringFromElement, _clzBldr.getImports()); + } + return argType; + } + /** * Extract <property> elements properties. * @@ -410,7 +433,7 @@ private List extractProperties(Element _propertyElement, Class } else if (attrType.contains("(")) { // contains structure String structPart = attrType.replaceAll("(\\(.+\\))", "$1"); - type = buildStructClass(structPart, "Property" + attrName + "Struct", _clzBldr, additionalClasses); + type = buildStructClass(structPart, "Property" + attrName + STRUCT_CLASS_SUFFIX, _clzBldr, additionalClasses); isStruct = true; } else { type = TypeConverter.getJavaTypeFromDBusType(attrType, _clzBldr.getImports()); @@ -444,34 +467,41 @@ private List extractProperties(Element _propertyElement, Class String rtnType = origType != null ? origType : clzzName; - ClassMethod classMethod = new ClassMethod( - ("boolean".equalsIgnoreCase(clzzName) ? "is" : "get") + attrName, rtnType, false); + String methodPrefix = "boolean".equalsIgnoreCase(clzzName) ? "is" : "get"; + ClassMethod classMethod = new ClassMethod(attrName, rtnType, methodPrefix, false); _clzBldr.getMethods().add(classMethod); + AnnotArgs annotArgs = AnnotArgs.create(); + if (propertyTypeRef != null) { - classMethod.getAnnotations().add("@" + DBusBoundProperty.class.getSimpleName() + "(type = " + propertyTypeRef.getClassName() + ".class)"); + annotArgs.add("type", propertyTypeRef.getClassName() + ".class"); } else if (isStruct) { - classMethod.getAnnotations().add("@" + DBusBoundProperty.class.getSimpleName() + "(type = " + clzzName + ".class)"); - } else { - classMethod.getAnnotations().add("@" + DBusBoundProperty.class.getSimpleName()); + annotArgs.add("type", clzzName + ".class"); } + + classMethod.getAnnotations().add(new AnnotationInfo(DBusBoundProperty.class, annotArgs)); + _clzBldr.getImports().add(DBusBoundProperty.class.getName()); } if (DBusProperty.Access.WRITE.getAccessName().equals(attrAccess) || DBusProperty.Access.READ_WRITE.getAccessName().equals(attrAccess)) { - ClassMethod classMethod = new ClassMethod("set" + attrName, "void", false); + + ClassMethod classMethod = new ClassMethod(attrName, "void", "set", false); classMethod.getArguments().add(new MemberOrArgument(attrName.substring(0, 1).toLowerCase() + attrName.substring(1), clzzName)); _clzBldr.getMethods().add(classMethod); - classMethod.getAnnotations().add("@" + DBusBoundProperty.class.getSimpleName()); + + classMethod.getAnnotations().add(new AnnotationInfo(DBusBoundProperty.class, null)); + _clzBldr.getImports().add(DBusBoundProperty.class.getName()); } } else { - String annotationParams = "name = \"" + attrName + "\", " - + "type = " + clzzName + ".class, " - + "access = " + DBusProperty.Access.class.getSimpleName() + "." + access; + AnnotArgs annotArgs = AnnotArgs.create() + .add("name", attrName) + .add("type", clzzName) + .add("access", DBusProperty.Access.class.getSimpleName() + "." + access); - AnnotationInfo annotationInfo = new AnnotationInfo(DBusProperty.class, annotationParams); + AnnotationInfo annotationInfo = new AnnotationInfo(DBusProperty.class, annotArgs); _clzBldr.getAnnotations().add(annotationInfo); } @@ -511,7 +541,7 @@ private String createTuple(List _outputArgs, String _className String genericName = findNextGenericName(genericTypes.keySet()); genericTypes.put(genericName, entry.getType()); - entry.getAnnotations().add("@Position(" + position++ + ")"); + entry.getAnnotations().add(new AnnotationInfo(Position.class, AnnotArgs.create().add(position++))); entry.setType(genericName); cnstrctArgs.add(new MemberOrArgument(entry.getName(), genericName)); } @@ -587,7 +617,7 @@ private String buildStructClass(List _dbusTypeStr, String _structName, String structClassName; if (data.dbusSig().contains("(")) { - String subStructFqcn = structFqcn + Util.upperCaseFirstChar(Objects.toString(data.name(), "")) + "Struct"; + String subStructFqcn = structFqcn + Util.upperCaseFirstChar(Objects.toString(data.name(), "")) + STRUCT_CLASS_SUFFIX; structClassName = new StructTreeBuilder(argumentPrefix, generatedStructClassNames) .buildStructClasses(data.dbusSig(), subStructFqcn, _packageName, _structClasses); } else { @@ -597,9 +627,8 @@ private String buildStructClass(List _dbusTypeStr, String _structName, } MemberOrArgument argument = new MemberOrArgument(data.name(), structClassName, true); - argument.getAnnotations().add("@Position(" + i + ")"); + argument.getAnnotations().add(new AnnotationInfo(Position.class, AnnotArgs.create().add(i))); root.getMembers().add(argument); - root.getImports().add(Position.class.getName()); classConstructor.getArguments().add(new MemberOrArgument(data.name(), structClassName)); } diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/StructTreeBuilder.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/StructTreeBuilder.java index e7927f776..a506694a8 100644 --- a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/StructTreeBuilder.java +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/StructTreeBuilder.java @@ -5,6 +5,8 @@ import org.freedesktop.dbus.annotations.Position; import org.freedesktop.dbus.exceptions.DBusException; import org.freedesktop.dbus.utils.Util; +import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.AnnotationInfo; +import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.AnnotationInfo.AnnotArgs; import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.ClassConstructor; import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.ClassType; import org.freedesktop.dbus.utils.generator.ClassBuilderInfo.MemberOrArgument; @@ -137,7 +139,7 @@ private ClassBuilderInfo createNested(List _list, String _structFqcn for (StructTree inTree : _list) { MemberOrArgument member = new MemberOrArgument("member" + position, inTree.getDataType().getName(), true); - member.getAnnotations().add("@Position(" + position + ")"); + member.getAnnotations().add(new AnnotationInfo(Position.class, AnnotArgs.create().add(position))); String constructorArg = "member" + position; @@ -181,8 +183,6 @@ private ClassBuilderInfo createNested(List _list, String _structFqcn retval = null; } - root.getImports().add(Position.class.getName()); // add position annotation as include - root.getImports().add(inTree.getDataType().getName()); root.getMembers().add(member); } diff --git a/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGeneratorTest.java b/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGeneratorTest.java index be7e130c0..5d592fb87 100644 --- a/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGeneratorTest.java +++ b/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/InterfaceCodeGeneratorTest.java @@ -17,10 +17,14 @@ class InterfaceCodeGeneratorTest { static InterfaceCodeGenerator loadDBusXmlFile(File _inputFile, String _objectPath, String _busName) { + return loadDBusXmlFile(false, _inputFile, _objectPath, _busName); + } + + static InterfaceCodeGenerator loadDBusXmlFile(boolean _createPropertyMethods, File _inputFile, String _objectPath, String _busName) { if (!Util.isBlank(_busName)) { String introspectionData = Util.readFileToString(_inputFile); - return new InterfaceCodeGenerator(false, introspectionData, _objectPath, _busName, null, false, null, false); + return new InterfaceCodeGenerator(false, introspectionData, _objectPath, _busName, null, _createPropertyMethods, null, false); } else { fail("No valid busName given"); } @@ -66,6 +70,66 @@ void testCreateNetworkManagerWirelessInterface() throws Exception { assertFalse(clzContent.contains("this._interfaceName")); } + @Test + void testHandleKebabCase() throws Exception { + InterfaceCodeGenerator ci2 = loadDBusXmlFile(true, + new File("src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.PowerProfileMonitor.xml"), + "/", "org.freedesktop.portal.PowerProfileMonitor"); + Map analyze = ci2.analyze(true); + + assertEquals(1, analyze.size()); + + String clzContent = analyze.get(analyze.keySet().iterator().next()); + + assertTrue(clzContent.contains("@DBusBoundProperty(name = \"power-saver-enabled\")")); + assertTrue(clzContent.contains("boolean isPowerSaverEnabled();")); + } + + @Test + void testHandleReservedMethodNames() throws Exception { + InterfaceCodeGenerator ci2 = loadDBusXmlFile(true, + new File("src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.Clipboard.xml"), + "/", "org.freedesktop.portal.Clipboard"); + Map analyze = ci2.analyze(true); + + assertEquals(1, analyze.size()); + + String clzContent = analyze.get(analyze.keySet().iterator().next()); + + assertTrue(clzContent.contains("@DBusMemberName(\"Serial\")")); + assertTrue(clzContent.contains("public UInt32 getSerialFromBus()")); + } + + @Test + void testHandleStructSignals() throws Exception { + InterfaceCodeGenerator ci2 = loadDBusXmlFile(true, + new File("src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.GlobalShortcuts.xml"), + "/", "org.freedesktop.portal.GlobalShortcuts"); + Map analyze = ci2.analyze(true); + + assertEquals(3, analyze.size()); + + String primaryFile = analyze.entrySet().stream() + .filter(e -> e.getKey().getName().equals("GlobalShortcuts.java")) + .findFirst() + .orElseThrow() + .getValue(); + + assertTrue(primaryFile.contains("public ShortcutsChanged(String path, DBusPath sessionHandle, List shortcuts) throws DBusException {")); + assertTrue(primaryFile.contains("private final List shortcuts;")); + assertTrue(primaryFile.contains("public List getShortcuts() {")); + + String secondaryFile = analyze.entrySet().stream() + .filter(e -> e.getKey().getName().equals("ShortcutsChangedShortcutsStruct.java")) + .findFirst() + .orElseThrow() + .getValue(); + + assertTrue(secondaryFile.contains("private final String member0;")); + assertTrue(secondaryFile.contains("private final Map> member1;")); + + } + @Test void testCreateSampleStructArgs() throws Exception { InterfaceCodeGenerator ci2 = loadDBusXmlFile( diff --git a/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.Clipboard.xml b/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.Clipboard.xml new file mode 100644 index 000000000..bff08821f --- /dev/null +++ b/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.Clipboard.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.GlobalShortcuts.xml b/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.GlobalShortcuts.xml new file mode 100644 index 000000000..26b2fb753 --- /dev/null +++ b/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.GlobalShortcuts.xml @@ -0,0 +1,279 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.PowerProfileMonitor.xml b/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.PowerProfileMonitor.xml new file mode 100644 index 000000000..ecd748ed9 --- /dev/null +++ b/dbus-java-utils/src/test/resources/CreateInterface/xdg-desktop/org.freedesktop.portal.PowerProfileMonitor.xml @@ -0,0 +1,45 @@ + + + + + + + + + + +