diff --git a/dbus-java-utils/src/main/java/module-info.java b/dbus-java-utils/src/main/java/module-info.java index eae5f460..20289c62 100644 --- a/dbus-java-utils/src/main/java/module-info.java +++ b/dbus-java-utils/src/main/java/module-info.java @@ -1,12 +1,15 @@ module org.freedesktop.dbus.utils { exports org.freedesktop.dbus.utils.bin; exports org.freedesktop.dbus.utils.generator; + exports org.freedesktop.dbus.utils.generator.type; exports org.freedesktop.dbus.viewer; - + requires transitive org.freedesktop.dbus; - + requires java.xml; requires java.desktop; - + opens org.freedesktop.dbus.utils.generator; + opens org.freedesktop.dbus.utils.generator.type; + } \ No newline at end of file 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 deleted file mode 100644 index 7834e122..00000000 --- a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfo.java +++ /dev/null @@ -1,897 +0,0 @@ -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.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -/** - * Helper to create Java class/interface files with proper formatting. - * - * @author hypfvieh - * @since v3.0.1 - 2018-12-22 - */ -public class ClassBuilderInfo { - - 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. */ - private final List annotations = new ArrayList<>(); - /** Members/Fields of this class. */ - private final List members = new ArrayList<>(); - /** Interfaces implemented by this class. */ - private final Set implementedInterfaces = new LinkedHashSet<>(); - /** Methods provided by this class. */ - private final List methods = new ArrayList<>(); - /** Inner classes inside of this class. */ - private final List innerClasses = new ArrayList<>(); - /** Constructors for this class. */ - private final List constructors = new ArrayList<>(); - - private final List generics = new ArrayList<>(); - - /** Prefix to prepend to method/constructor arguments. */ - private final String argumentPrefix; - - /** Name of this class. */ - private String className; - /** Package of this class. */ - private String packageName; - - /** Package name used by DBus. */ - private String dbusPackageName; - - /** Type of this class (interface or class). */ - private ClassType classType; - /** Class which this class may extend. */ - private String extendClass; - - /** - * Create new instance without argument prefix. - */ - public ClassBuilderInfo() { - this(null); - } - - /** - * Create new instance. - * @param _argumentPrefix prepend given prefix to all method/constructor arguments - */ - public ClassBuilderInfo(String _argumentPrefix) { - argumentPrefix = _argumentPrefix; - } - - public Set getImports() { - return imports; - } - - public String getPackageName() { - return packageName; - } - - public void setPackageName(String _packageName) { - packageName = _packageName; - } - - public String getDbusPackageName() { - return dbusPackageName; - } - - public void setDbusPackageName(String _dbusPackageName) { - dbusPackageName = _dbusPackageName; - } - - public String getClassName() { - return className; - } - - public void setClassName(String _className) { - className = _className; - } - - public ClassType getClassType() { - return classType; - } - - public void setClassType(ClassType _classType) { - classType = _classType; - } - - public List getAnnotations() { - return annotations; - } - - public Set getImplementedInterfaces() { - return implementedInterfaces; - } - - public String getExtendClass() { - return extendClass; - } - - public void setExtendClass(String _extendClass) { - extendClass = _extendClass; - } - - public List getMethods() { - return methods; - } - - public List getMembers() { - return members; - } - - public List getInnerClasses() { - return innerClasses; - } - - public List getConstructors() { - return constructors; - } - - public List getGenerics() { - return generics; - } - - /** - * Create the Java source for the class information provided. - * - * @return String - */ - public String createClassFileContent() { - List result = createClassFileContent(false, new LinkedHashSet<>()); - return String.join(System.lineSeparator(), result) + System.lineSeparator(); - } - - /** - * Create the Java source for the class information provided. - * - * @param _staticClass this is static inner class - * @param _argumentPrefix use given prefix for generated method arguments (null/blank if not needed) - * @param _otherImports this class needs additional imports (e.g. due to inner class) - * @return - */ - private List createClassFileContent(boolean _staticClass, Set _otherImports) { - - 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(""); - content.add("/**"); - content.add(" * Auto-generated class."); - content.add(" */"); - - } else { - addEmptyLineIfNeeded(content); - } - - if (getDbusPackageName() != null) { - allImports.add(DBusInterfaceName.class.getName()); - content.add(classIndent + "@" + DBusInterfaceName.class.getSimpleName() + "(\"" + getDbusPackageName() + "\")"); - } - - 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() + ")"; - } - content.add(annotationCode); - } - - String bgn = classIndent + "public " + (_staticClass ? "static " : "") + (getClassType() == ClassType.INTERFACE ? "interface" : "class"); - bgn += " " + getClassName(); - if (!getGenerics().isEmpty()) { - bgn += "<" + String.join(", ", getGenerics()) + ">"; - } - if (getExtendClass() != null) { - Set lImports = getImportsForType(getExtendClass()); - getImports().addAll(lImports); - allImports.addAll(lImports); - bgn += " extends " + getSimpleTypeClasses(getExtendClass()); - } - if (!getImplementedInterfaces().isEmpty()) { - bgn += " implements " + getImplementedInterfaces().stream().map(Util::extractClassNameFromFqcn).collect(Collectors.joining(", ")); - // add classes import if implements-arguments are not a java.lang. classes - getImports().addAll(getImplementedInterfaces().stream().filter(s -> !s.startsWith("java.lang.")).toList()); - } - - bgn += " {"; - - content.add(bgn); - if (_staticClass) { - addEmptyLineIfNeeded(content); - } - - // add member fields - for (MemberOrArgument member : members) { - if (!member.getAnnotations().isEmpty()) { - 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) + ";"); - } - - if (!getConstructors().isEmpty()) { - for (ClassConstructor constructor : getConstructors()) { - addEmptyLineIfNeeded(content); - String outerIndent = _staticClass ? DEFAULT_INDENT.repeat(2) : DEFAULT_INDENT; - content.addAll(constructor.generatedCode(outerIndent, getClassName(), argumentPrefix, allImports)); - } - } - - // add getter and setter - for (MemberOrArgument member : members) { - addEmptyLineIfNeeded(content); - content.addAll(member.generateCode(memberIndent, argumentPrefix, allImports)); - } - - for (ClassMethod mth : getMethods()) { - addEmptyLineIfNeeded(content); - content.addAll(mth.generateCode(getClassType() == ClassType.INTERFACE, argumentPrefix, memberIndent, allImports)); - } - - for (ClassBuilderInfo inner : getInnerClasses()) { - addEmptyLineIfNeeded(content); - content.addAll(inner.createClassFileContent(true, allImports)); - allImports.addAll(inner.getImports()); // collect additional imports which may have been added by inner class - } - - addEmptyLineIfNeeded(content); - - content.add(classIndent + "}"); - - // write imports to resulting content if this is not a inner class - if (!_staticClass) { - content.add(2, ""); - content.addAll(2, allImports.stream() - .filter(l -> !l.startsWith("java.lang.")) // do not include imports for 'java.lang' - .filter(l -> !l.replaceFirst("(.+)\\..+", "$1").equals(getPackageName())) // do not add imports for classes in same package - .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; - } - - private static String maybePrefix(String _arg, String _prefix) { - return Util.isBlank(_prefix) ? _arg : _prefix + _arg; - } - - private static void addEmptyLineIfNeeded(List _content) { - if (_content == null || _content.isEmpty()) { - return; - } - - String lastLine = _content.getLast(); - if (!Util.isBlank(lastLine)) { - _content.add(""); - } - } - - /** - * Create the filename with path this java class should use. - * - * @return String, null if class name was null - */ - public String getFileName() { - if (getClassName() == null) { // no class name - return null; - } - if (getPackageName() == null) { // no package name - return getClassName() + ".java"; - } - - return getPackageName().replace(".", File.separator) + File.separator + getClassName() + ".java"; - } - - /** - * Creates the fully qualified classname based on the provided classname and package. - * - * @return String - */ - public String getFqcn() { - return Util.isBlank(getPackageName()) ? getClassName() : getPackageName() + "." + getClassName(); - } - - @Override - public String toString() { - return getClass().getSimpleName() + " [imports=" + imports + ", annotations=" + annotations + ", members=" + members - + ", implementedInterfaces=" + implementedInterfaces + ", methods=" + methods + ", innerClasses=" - + innerClasses + ", constructors=" + constructors + ", className=" + className + ", packageName=" - + packageName + ", dbusPackageName=" + dbusPackageName + ", classType=" + classType + ", extendClass=" - + extendClass + "]"; - } - - /** - * Simplify class names in the type. Please go to unit tests for usage examples. - * - * @param _type type described in the string format - * @return type with simplified class names - */ - static String getSimpleTypeClasses(String _type) { - if (_type == null) { - return null; - } - StringBuilder sb = new StringBuilder(); - Pattern compile = Pattern.compile("([^, <>?]+)"); - Matcher matcher = compile.matcher(_type); - while (matcher.find()) { - String match = matcher.group(); - matcher.appendReplacement(sb, Util.extractClassNameFromFqcn(match)); - } - matcher.appendTail(sb); - return sb.toString(); - } - - /** - * Get all classes that should be imported for the input type, this method works fine with generic types. - * Please go to unit tests for usage examples. - * - * @param _type type described in the string format - * @return set of classes required for imports - */ - static Set getImportsForType(String _type) { - Set imports = new HashSet<>(); - if (!_type.contains("<")) { - if (!_type.startsWith("java.lang.") && _type.contains(".")) { - imports.add(_type); - } - return imports; - } - Pattern compile = Pattern.compile("([^, <>?]+)"); - Matcher matcher = compile.matcher(_type); - while (matcher.find()) { - String match = matcher.group(); - - if (!match.startsWith("java.lang.") && match.contains(".")) { - imports.add(match); - } - } - 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. - * - * @author hypfvieh - * @since v3.2.1 - 2019-11-13 - */ - public static class AnnotationInfo { - - /** Annotation class. */ - private final Class annotationClass; - /** Map of parameters for the annotation (should be ordered). */ - private final Map annotationParams = new LinkedHashMap<>(); - - private final Set> additionalImports = new LinkedHashSet<>(); - - public AnnotationInfo(Class _annotationClass, AnnotArgs _annotationParams) { - annotationClass = _annotationClass; - 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 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 && !s.endsWith(".class")) { - 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); - } - } - } - } - - /** - * Pojo which represents a class method. - * - * @author hypfvieh - * @since v3.0.1 - 2018-12-20 - */ - public static class ClassMethod { - - private static final String METHOD_TEMPL = """ - %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<>(); - /** List of annotations for this method. */ - 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() { - return name; - } - - public String getReturnType() { - return returnType; - } - - public String getMethodPrefix() { - return methodPrefix; - } - - public boolean isFinalMethod() { - return finalMethod; - } - - public List getArguments() { - return arguments; - } - - public List getAnnotations() { - return annotations; - } - - public List generateCode(boolean _isInterface, String _argumentPrefix, String _indent, Set _allImports) { - List result = new ArrayList<>(); - - 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(", ")); - } - - 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; - } - - } - - /** - * Pojo which represents a class member/field or argument. - * - * @author hypfvieh - * @since v3.0.1 - 2018-12-20 - */ - 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; - } - """; - - private static final String SETTER_TEMPL = """ - public void set%s(%s %s) { - %s%s = %s; - } - """; - - /** Name of member/field. */ - private final String name; - /** Type of member/field (e.g. String, int...). */ - private String type; - /** True to force this member to be final, false otherwise. */ - private final boolean finalArg; - /** 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<>(); - - public MemberOrArgument(String _name, String _type, boolean _finalMember) { - // repair reserved words by adding 'Param' as appendix, and when start with _ too - name = (IdentifierMangler.isReservedWord(_name) || IdentifierMangler.isReservedWord(_name.replaceFirst("^_(.+)", "$1"))) ? _name + "param" : _name; - type = _type; - finalArg = _finalMember; - } - - public MemberOrArgument(String _name, String _type) { - this(_name, _type, false); - } - - public List getAnnotations() { - return annotations; - } - - public String getName() { - return name; - } - - public String getType() { - return type; - } - - public void setType(String _type) { - type = _type; - } - - public boolean isFinalArg() { - return finalArg; - } - - public List getGenerics() { - return generics; - } - - public String getFullType(Set _allImports) { - StringBuilder sb = new StringBuilder(); - sb.append(TypeConverter.getProperJavaClass(getType(), _allImports)); - - if (!getGenerics().isEmpty()) { - sb.append("<") - .append(getGenerics().stream().map(c -> TypeConverter.convertJavaType(c, false)).collect(Collectors.joining(", "))) - .append(">"); - } - - return sb.toString(); - } - - public String asOneLineString(Set _allImports, String _prefix, boolean _includeAnnotations) { - StringBuilder sb = new StringBuilder(); - - if (isFinalArg()) { - sb.append("final "); - } - - if (_includeAnnotations && !getAnnotations().isEmpty()) { - sb.append(String.join(" ", getAnnotations().stream().map(e -> e.getAnnotationString()).toList())) - .append(" "); - } - - sb.append(getFullType(_allImports)); - - sb.append(" "); - - sb.append(maybePrefix(getName(), _prefix)); - - return sb.toString(); - } - - public List generateCode(String _indent, String _prefix, Set _allImports) { - List result = new ArrayList<>(); - String memberType = TypeConverter.getProperJavaClass(getType(), _allImports); - - if (!getGenerics().isEmpty()) { - memberType += "<" + getGenerics().stream().map(c -> TypeConverter.convertJavaType(c, false)).collect(Collectors.joining(", ")) + ">"; - } - - 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); - - return result; - } - - @Override - public String toString() { - return getClass().getSimpleName() + " [name=" + name + ", type=" + type + ", finalArg=" + finalArg + ", generics=" - + generics + ", annotations=" + annotations + "]"; - } - - } - - /** - * Pojo which represents a class constructor. - * - * @author hypfvieh - * @since v3.0.1 - 2018-12-20 - */ - public static class ClassConstructor { - private static final String CONSTRUCTOR_TEMPL = """ - public %s(%s)%s { - %s - } - """; - - /** Map of arguments for the constructor. Key is argument name, value is argument type. */ - private final List arguments = new ArrayList<>(); - /** Map of arguments for the super-constructor. Key is argument name, value is argument type. */ - private final List superArguments = new ArrayList<>(); - - /** List of throws arguments. */ - private final List throwArguments = new ArrayList<>(); - - public List getThrowArguments() { - return throwArguments; - } - - public List getArguments() { - return arguments; - } - - public List getSuperArguments() { - return superArguments; - } - - public String argumentsAsString(Set _allImports, String _prefix) { - return getArguments().stream().map(a -> a.asOneLineString(_allImports, _prefix, true)).collect(Collectors.joining(", ")); - } - - public List generatedCode(String _indent, String _className, String _argumentPrefix, Set _allImports) { - - List filteredSuperArguments = new ArrayList<>(getSuperArguments()); - filteredSuperArguments.removeIf(e -> getArguments().contains(e)); - String constructorArgs = ""; - - if (!filteredSuperArguments.isEmpty()) { - constructorArgs += filteredSuperArguments.stream().map(e -> e.asOneLineString(_allImports, _argumentPrefix, false)).collect(Collectors.joining(", ")); - if (!getArguments().isEmpty()) { - constructorArgs += ", "; - } - } - - if (!getArguments().isEmpty()) { - constructorArgs += argumentsAsString(_allImports, _argumentPrefix); - } - - String throwArgs = getThrowArguments().isEmpty() ? "" : (" throws " + String.join(", ", getThrowArguments())); - - String assignments = ""; - - if (!getSuperArguments().isEmpty()) { - assignments = " ".repeat(_indent.length() / 2) + "super(" + getSuperArguments().stream() - .map(e -> maybePrefix(e.getName(), _argumentPrefix)) - .collect(Collectors.joining(", ")) + ");" + System.lineSeparator(); - } - - if (!getArguments().isEmpty()) { - List assigns = new ArrayList<>(); - String innerIndent = " ".repeat(_indent.length() / 2); - for (MemberOrArgument e : getArguments()) { - assigns.add(innerIndent + "this." + e.getName() + " = " + maybePrefix(e.getName(), _argumentPrefix) + ";"); - } - assignments += String.join(System.lineSeparator(), assigns); - } - - return CONSTRUCTOR_TEMPL.formatted(_className, constructorArgs, throwArgs, assignments) - .lines().map(l -> _indent + l).toList(); - - } - - @Override - public String toString() { - return getClass().getSimpleName() + " [arguments=" + arguments + ", superArguments=" + superArguments - + ", throwArguments=" + throwArguments + "]"; - } - - } - - /** - * Enum to define either the {@link ClassBuilderInfo} is for a CLASS or an INTERFACE. - * - * @author hypfvieh - * @since v3.0.1 - 2018-12-20 - */ - public enum ClassType { - INTERFACE, - CLASS; - } - -} 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 8d300bd4..af9d78a1 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 @@ -18,8 +18,10 @@ import org.freedesktop.dbus.types.Variant; 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.freedesktop.dbus.utils.generator.type.*; +import org.freedesktop.dbus.utils.generator.type.AnnotationInfo.AnnotArgs; +import org.freedesktop.dbus.utils.generator.type.AnnotationInfo.AnnotArgs.AnnotClass; +import org.freedesktop.dbus.utils.generator.type.ClassBuilderInfo.ClassType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -250,10 +252,9 @@ private List extractSignals(Element _signalElement, ClassBuild int unknownArgCnt = 0; for (Element argElm : signalArgs) { - // _clzBldr, additionalClasses, methodElementName, argElm, argName String argName = Util.snakeToCamelCase(argElm.getAttribute("name")); - String argType = extractOrCreateArgType(_clzBldr, additionalClasses, className, argElm.getAttribute("type"), argName); + String argType = extractOrCreateArgType(_clzBldr, additionalClasses, className, argElm.getAttribute("type"), argName); TypeConverter.getJavaTypeFromDBusType(argElm.getAttribute("type"), _clzBldr.getImports()); if (Util.isBlank(argName)) { argName = "arg" + unknownArgCnt; @@ -263,18 +264,18 @@ private List extractSignals(Element _signalElement, ClassBuild } for (Entry argEntry : args.entrySet()) { - innerClass.getMembers().add(new MemberOrArgument(argEntry.getKey(), argEntry.getValue(), true)); - argsList.add(new MemberOrArgument(argEntry.getKey(), argEntry.getValue(), false)); + innerClass.getMembers().add(new MemberOrArgument(innerClass, argEntry.getKey(), argEntry.getValue(), true)); + argsList.add(new MemberOrArgument(innerClass, argEntry.getKey(), argEntry.getValue(), false)); } } - ClassConstructor classConstructor = new ClassBuilderInfo.ClassConstructor(); + ClassConstructor classConstructor = new ClassConstructor(_clzBldr, className); classConstructor.getArguments().addAll(argsList); classConstructor.getThrowArguments().add(DBusException.class.getSimpleName()); - classConstructor.getSuperArguments().add(new MemberOrArgument("path", "String", false)); + classConstructor.getSuperArguments().add(new MemberOrArgument(_clzBldr, "path", "String", false)); classConstructor.getSuperArguments().addAll(argsList); innerClass.getConstructors().add(classConstructor); @@ -322,9 +323,9 @@ private List extractMethods(Element _methodElement, ClassBuild String dirAttr = argElm.getAttribute("direction"); if ("in".equals(dirAttr) || "".equals(dirAttr)) { - inputArgs.add(new MemberOrArgument(argName, TypeConverter.getProperJavaClass(argType, _clzBldr.getImports()))); + inputArgs.add(new MemberOrArgument(_clzBldr, argName, TypeConverter.getProperJavaClass(argType, _clzBldr.getImports()))); } else if ("out".equals(dirAttr)) { - outputArgs.add(new MemberOrArgument(argName, TypeConverter.getProperJavaClass(argType, _clzBldr.getImports()), false)); + outputArgs.add(new MemberOrArgument(_clzBldr, argName, TypeConverter.getProperJavaClass(argType, _clzBldr.getImports()), false)); dbusOutputArgTypes.add(argType); dbusSignatures.add(new DuoData(argElm.getAttribute("type"), argName)); } @@ -360,14 +361,14 @@ private List extractMethods(Element _methodElement, ClassBuild } if (resultType != null) { - ClassMethod classMethod = new ClassMethod(methodElementName, resultType, false); + ClassMethod classMethod = new ClassMethod(_clzBldr, methodElementName, resultType, false); classMethod.getArguments().addAll(inputArgs); _clzBldr.getMethods().add(classMethod); } } else { // method has no arguments - ClassMethod classMethod = new ClassMethod(methodElementName, "void", false); + ClassMethod classMethod = new ClassMethod(_clzBldr, methodElementName, "void", false); _clzBldr.getMethods().add(classMethod); } @@ -461,22 +462,21 @@ private List extractProperties(Element _propertyElement, Class clzzName = _clzBldr.getClassName() + "." + typeRefInterfaceName; } + String rtnType = origType != null ? origType : clzzName; + if (propertyMethods) { if (DBusProperty.Access.READ.getAccessName().equals(attrAccess) || DBusProperty.Access.READ_WRITE.getAccessName().equals(attrAccess)) { - String rtnType = origType != null ? origType : clzzName; - - String methodPrefix = "boolean".equalsIgnoreCase(clzzName) ? "is" : "get"; - ClassMethod classMethod = new ClassMethod(attrName, rtnType, methodPrefix, false); + ClassMethod classMethod = new GetterMethod(_clzBldr, attrName, rtnType); _clzBldr.getMethods().add(classMethod); AnnotArgs annotArgs = AnnotArgs.create(); if (propertyTypeRef != null) { - annotArgs.add("type", propertyTypeRef.getClassName() + ".class"); + annotArgs.add("type", AnnotClass.of(propertyTypeRef.getClassName())); } else if (isStruct) { - annotArgs.add("type", clzzName + ".class"); + annotArgs.add("type", AnnotClass.of(clzzName)); } classMethod.getAnnotations().add(new AnnotationInfo(DBusBoundProperty.class, annotArgs)); @@ -487,8 +487,9 @@ private List extractProperties(Element _propertyElement, Class if (DBusProperty.Access.WRITE.getAccessName().equals(attrAccess) || DBusProperty.Access.READ_WRITE.getAccessName().equals(attrAccess)) { - ClassMethod classMethod = new ClassMethod(attrName, "void", "set", false); - classMethod.getArguments().add(new MemberOrArgument(attrName.substring(0, 1).toLowerCase() + attrName.substring(1), clzzName)); + ClassMethod classMethod = new SetterMethod(_clzBldr, attrName, rtnType); + classMethod.getArguments().add(new MemberOrArgument(_clzBldr, attrName.substring(0, 1).toLowerCase() + + attrName.substring(1), clzzName)); _clzBldr.getMethods().add(classMethod); classMethod.getAnnotations().add(new AnnotationInfo(DBusBoundProperty.class, null)); @@ -543,10 +544,10 @@ private String createTuple(List _outputArgs, String _className genericTypes.put(genericName, entry.getType()); entry.getAnnotations().add(new AnnotationInfo(Position.class, AnnotArgs.create().add(position++))); entry.setType(genericName); - cnstrctArgs.add(new MemberOrArgument(entry.getName(), genericName)); + cnstrctArgs.add(new MemberOrArgument(_parentClzBldr, entry.getName(), genericName)); } - ClassConstructor cnstrct = new ClassConstructor(); + ClassConstructor cnstrct = new ClassConstructor(_parentClzBldr, _className); cnstrct.getArguments().addAll(cnstrctArgs); info.getConstructors().add(cnstrct); @@ -599,18 +600,19 @@ private String buildStructClass(String _dbusTypeStr, String _structName, ClassBu return structClassName; } - private String buildStructClass(List _dbusTypeStr, String _structName, ClassBuilderInfo _packageName, List _structClasses) throws DBusException { + private String buildStructClass(List _dbusTypeStr, String _structName, ClassBuilderInfo _clzBldr, List _structClasses) throws DBusException { + String className = Util.upperCaseFirstChar(_structName); ClassBuilderInfo root = new ClassBuilderInfo(argumentPrefix); - root.setPackageName(_packageName.getPackageName()); - root.setClassName(Util.upperCaseFirstChar(_structName)); + root.setPackageName(_clzBldr.getPackageName()); + root.setClassName(className); root.setExtendClass(Struct.class.getName()); root.setClassType(ClassType.CLASS); - ClassConstructor classConstructor = new ClassConstructor(); + ClassConstructor classConstructor = new ClassConstructor(_clzBldr, className); root.getConstructors().add(classConstructor); - String structFqcn = _packageName.getPackageName() + "." + Util.upperCaseFirstChar(_structName); + String structFqcn = _clzBldr.getPackageName() + "." + Util.upperCaseFirstChar(_structName); for (int i = 0; i < _dbusTypeStr.size(); i++) { DuoData data = _dbusTypeStr.get(i); @@ -619,18 +621,18 @@ private String buildStructClass(List _dbusTypeStr, String _structName, if (data.dbusSig().contains("(")) { String subStructFqcn = structFqcn + Util.upperCaseFirstChar(Objects.toString(data.name(), "")) + STRUCT_CLASS_SUFFIX; structClassName = new StructTreeBuilder(argumentPrefix, generatedStructClassNames) - .buildStructClasses(data.dbusSig(), subStructFqcn, _packageName, _structClasses); + .buildStructClasses(data.dbusSig(), subStructFqcn, _clzBldr, _structClasses); } else { Set addClasses = new HashSet<>(); structClassName = TypeConverter.getJavaTypeFromDBusType(data.dbusSig(), addClasses); root.getImports().addAll(addClasses); } - MemberOrArgument argument = new MemberOrArgument(data.name(), structClassName, true); + MemberOrArgument argument = new MemberOrArgument(_clzBldr, data.name(), structClassName, true); argument.getAnnotations().add(new AnnotationInfo(Position.class, AnnotArgs.create().add(i))); root.getMembers().add(argument); - classConstructor.getArguments().add(new MemberOrArgument(data.name(), structClassName)); + classConstructor.getArguments().add(new MemberOrArgument(_clzBldr, data.name(), structClassName)); } _structClasses.add(root); 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 a506694a..0552d29f 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,11 +5,12 @@ 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; +import org.freedesktop.dbus.utils.generator.type.AnnotationInfo; +import org.freedesktop.dbus.utils.generator.type.AnnotationInfo.AnnotArgs; +import org.freedesktop.dbus.utils.generator.type.ClassBuilderInfo; +import org.freedesktop.dbus.utils.generator.type.ClassBuilderInfo.ClassType; +import org.freedesktop.dbus.utils.generator.type.ClassConstructor; +import org.freedesktop.dbus.utils.generator.type.MemberOrArgument; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; @@ -134,11 +135,11 @@ private ClassBuilderInfo createNested(List _list, String _structFqcn ClassBuilderInfo root = _root; ClassBuilderInfo retval = null; - ClassConstructor classConstructor = new ClassConstructor(); + ClassConstructor classConstructor = new ClassConstructor(_root, root.getClassName()); for (StructTree inTree : _list) { - MemberOrArgument member = new MemberOrArgument("member" + position, inTree.getDataType().getName(), true); + MemberOrArgument member = new MemberOrArgument(_root, "member" + position, inTree.getDataType().getName(), true); member.getAnnotations().add(new AnnotationInfo(Position.class, AnnotArgs.create().add(position))); String constructorArg = "member" + position; @@ -155,7 +156,7 @@ private ClassBuilderInfo createNested(List _list, String _structFqcn temp.setPackageName(_root.getPackageName()); temp.setExtendClass(Struct.class.getName()); temp.setClassType(ClassType.CLASS); - classConstructor.getArguments().add(new MemberOrArgument(constructorArg, temp.getClassName())); + classConstructor.getArguments().add(new MemberOrArgument(_root, constructorArg, temp.getClassName())); member.setType(temp.getClassName()); createNested(inTree.getSubType(), _structFqcnBase, temp, _classes); @@ -174,12 +175,12 @@ private ClassBuilderInfo createNested(List _list, String _structFqcn } root.getImports().addAll(temp.getImports()); - MemberOrArgument argument = new MemberOrArgument(constructorArg, inTree.getDataType().getName()); + MemberOrArgument argument = new MemberOrArgument(_root, constructorArg, inTree.getDataType().getName()); argument.getGenerics().addAll(member.getGenerics()); classConstructor.getArguments().add(argument); retval = null; } else { - classConstructor.getArguments().add(new MemberOrArgument(constructorArg, inTree.getDataType().getName())); + classConstructor.getArguments().add(new MemberOrArgument(_root, constructorArg, inTree.getDataType().getName())); retval = null; } diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/AnnotationInfo.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/AnnotationInfo.java new file mode 100644 index 00000000..c64303d0 --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/AnnotationInfo.java @@ -0,0 +1,153 @@ +package org.freedesktop.dbus.utils.generator.type; + +import org.freedesktop.dbus.utils.generator.type.AnnotationInfo.AnnotArgs.AnnotClass; + +import java.lang.annotation.Annotation; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Contains information about annotation to place on classes, members or methods. + * + * @author hypfvieh + * @since v3.2.1 - 2019-11-13 + */ +public class AnnotationInfo { + + /** Annotation class. */ + private final Class annotationClass; + /** Map of parameters for the annotation (should be ordered). */ + private final Map annotationParams = new LinkedHashMap<>(); + + private final Set> additionalImports = new LinkedHashSet<>(); + + public AnnotationInfo(Class _annotationClass, AnnotArgs _annotationParams) { + annotationClass = _annotationClass; + 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 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 { + sb.append(getAnnotationParams().entrySet().stream() + .map(e -> "%s = %s".formatted(e.getKey(), handleArg(e.getValue()))) + .collect(Collectors.joining(", "))); + } + + sb.append(")"); + } + + return sb.toString(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + " [annotationClass=" + annotationClass + + ", annotationParams=" + annotationParams + + ", additionalImports=" + additionalImports + "]"; + } + + private String handleArg(Object _value) { + if (_value instanceof AnnotClass ct) { + return ct.fqcn() + ".class"; + } + if (_value instanceof String s && !s.endsWith(".class")) { + 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; + } + + public AnnotArgs add(String _key, AnnotClass _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(); + } + + public record AnnotClass(String fqcn) { + public static AnnotClass of(String _fqcn) { + return new AnnotClass(_fqcn); + } + } + + 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); + } + } + } +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassBuilderInfo.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassBuilderInfo.java new file mode 100644 index 00000000..fd9bde8e --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassBuilderInfo.java @@ -0,0 +1,424 @@ +package org.freedesktop.dbus.utils.generator.type; + +import org.freedesktop.dbus.annotations.DBusInterfaceName; +import org.freedesktop.dbus.messages.Message; +import org.freedesktop.dbus.utils.Util; +import org.slf4j.LoggerFactory; + +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.io.File; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * Helper to create Java class/interface files with proper formatting. + * + * @author hypfvieh + * @since v3.0.1 - 2018-12-22 + */ +public class ClassBuilderInfo { + + static final String DEFAULT_INDENT = " "; + + private static final Set RESERVED_METHOD_NAMES = getReservedMethods(Message.class); + + /** Imported files for this class. */ + private final Set imports = new TreeSet<>(); + /** Annotations of this class. */ + private final List annotations = new ArrayList<>(); + /** Members/Fields of this class. */ + private final List members = new ArrayList<>(); + /** Interfaces implemented by this class. */ + private final Set implementedInterfaces = new LinkedHashSet<>(); + /** Methods provided by this class. */ + private final List methods = new ArrayList<>(); + /** Inner classes inside of this class. */ + private final List innerClasses = new ArrayList<>(); + /** Constructors for this class. */ + private final List constructors = new ArrayList<>(); + + private final List generics = new ArrayList<>(); + + /** Prefix to prepend to method/constructor arguments. */ + private final String argumentPrefix; + + /** Name of this class. */ + private String className; + /** Package of this class. */ + private String packageName; + + /** Package name used by DBus. */ + private String dbusPackageName; + + /** Type of this class (interface or class). */ + private ClassType classType; + /** Class which this class may extend. */ + private String extendClass; + + /** + * Create new instance without argument prefix. + */ + public ClassBuilderInfo() { + this(null); + } + + /** + * Create new instance. + * @param _argumentPrefix prepend given prefix to all method/constructor arguments + */ + public ClassBuilderInfo(String _argumentPrefix) { + argumentPrefix = _argumentPrefix; + } + + public Set getImports() { + return imports; + } + + public String getPackageName() { + return packageName; + } + + public void setPackageName(String _packageName) { + packageName = _packageName; + } + + public String getDbusPackageName() { + return dbusPackageName; + } + + public void setDbusPackageName(String _dbusPackageName) { + dbusPackageName = _dbusPackageName; + } + + public String getClassName() { + return className; + } + + public void setClassName(String _className) { + className = _className; + } + + public ClassType getClassType() { + return classType; + } + + public void setClassType(ClassType _classType) { + classType = _classType; + } + + public List getAnnotations() { + return annotations; + } + + public Set getImplementedInterfaces() { + return implementedInterfaces; + } + + public String getExtendClass() { + return extendClass; + } + + public void setExtendClass(String _extendClass) { + extendClass = _extendClass; + } + + public List getMethods() { + return methods; + } + + public String getArgumentPrefix() { + return argumentPrefix; + } + + public List getMembers() { + return members; + } + + public List getInnerClasses() { + return innerClasses; + } + + public List getConstructors() { + return constructors; + } + + public List getGenerics() { + return generics; + } + + /** + * Create the Java source for the class information provided. + * + * @return String + */ + public String createClassFileContent() { + List result = createClassFileContent(false, new LinkedHashSet<>()); + return String.join(System.lineSeparator(), result) + System.lineSeparator(); + } + + /** + * Create the Java source for the class information provided. + * + * @param _staticClass this is static inner class + * @param _argumentPrefix use given prefix for generated method arguments (null/blank if not needed) + * @param _otherImports this class needs additional imports (e.g. due to inner class) + * @return + */ + private List createClassFileContent(boolean _staticClass, Set _otherImports) { + + final int memberIndentCnt = _staticClass ? 2 : 1; + final String classIndent = ICodeGenerator.INDENT.repeat(_staticClass ? 1 : 0); + final String memberIndent = ICodeGenerator.INDENT.repeat(memberIndentCnt); + + 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(""); + content.add("/**"); + content.add(" * Auto-generated class."); + content.add(" */"); + + } else { + addEmptyLineIfNeeded(content); + } + + if (getDbusPackageName() != null) { + allImports.add(DBusInterfaceName.class.getName()); + content.add(classIndent + "@" + DBusInterfaceName.class.getSimpleName() + "(\"" + getDbusPackageName() + "\")"); + } + + 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() + ")"; + } + content.add(annotationCode); + } + + String bgn = classIndent + "public " + (_staticClass ? "static " : "") + (getClassType() == ClassType.INTERFACE ? "interface" : "class"); + bgn += " " + getClassName(); + if (!getGenerics().isEmpty()) { + bgn += "<" + String.join(", ", getGenerics()) + ">"; + } + if (getExtendClass() != null) { + Set lImports = getImportsForType(getExtendClass()); + getImports().addAll(lImports); + allImports.addAll(lImports); + bgn += " extends " + getSimpleTypeClasses(getExtendClass()); + } + if (!getImplementedInterfaces().isEmpty()) { + bgn += " implements " + getImplementedInterfaces().stream().map(Util::extractClassNameFromFqcn).collect(Collectors.joining(", ")); + // add classes import if implements-arguments are not a java.lang. classes + getImports().addAll(getImplementedInterfaces().stream().filter(s -> !s.startsWith("java.lang.")).toList()); + } + + bgn += " {"; + + content.add(bgn); + if (_staticClass) { + addEmptyLineIfNeeded(content); + } + + // add member fields + for (MemberOrArgument member : members) { + if (!member.getAnnotations().isEmpty()) { + 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(false) + ";"); + } + + if (!getConstructors().isEmpty()) { + for (ClassConstructor constructor : getConstructors()) { + addEmptyLineIfNeeded(content); + int outerIndent = _staticClass ? 2 : 1; + content.addAll(constructor.generate(outerIndent)); + } + } + + // add getter and setter + for (MemberOrArgument member : members) { + addEmptyLineIfNeeded(content); + content.addAll(member.generate(memberIndentCnt)); + } + + for (ClassMethod mth : getMethods()) { + addEmptyLineIfNeeded(content); + content.addAll(mth.generate(memberIndentCnt)); + } + + for (ClassBuilderInfo inner : getInnerClasses()) { + addEmptyLineIfNeeded(content); + content.addAll(inner.createClassFileContent(true, allImports)); + allImports.addAll(inner.getImports()); // collect additional imports which may have been added by inner class + } + + addEmptyLineIfNeeded(content); + + content.add(classIndent + "}"); + + // write imports to resulting content if this is not a inner class + if (!_staticClass) { + content.add(2, ""); + content.addAll(2, allImports.stream() + .filter(l -> !l.startsWith("java.lang.")) // do not include imports for 'java.lang' + .filter(l -> !l.replaceFirst("(.+)\\..+", "$1").equals(getPackageName())) // do not add imports for classes in same package + .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; + } + + static String maybePrefix(String _arg, String _prefix) { + return Util.isBlank(_prefix) ? _arg : _prefix + _arg; + } + + static void addEmptyLineIfNeeded(List _content) { + if (_content == null || _content.isEmpty()) { + return; + } + + String lastLine = _content.getLast(); + if (!Util.isBlank(lastLine)) { + _content.add(""); + } + } + + /** + * Create the filename with path this java class should use. + * + * @return String, null if class name was null + */ + public String getFileName() { + if (getClassName() == null) { // no class name + return null; + } + if (getPackageName() == null) { // no package name + return getClassName() + ".java"; + } + + return getPackageName().replace(".", File.separator) + File.separator + getClassName() + ".java"; + } + + /** + * Creates the fully qualified classname based on the provided classname and package. + * + * @return String + */ + public String getFqcn() { + return Util.isBlank(getPackageName()) ? getClassName() : getPackageName() + "." + getClassName(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [imports=" + imports + ", annotations=" + annotations + ", members=" + members + + ", implementedInterfaces=" + implementedInterfaces + ", methods=" + methods + ", innerClasses=" + + innerClasses + ", constructors=" + constructors + ", className=" + className + ", packageName=" + + packageName + ", dbusPackageName=" + dbusPackageName + ", classType=" + classType + ", extendClass=" + + extendClass + "]"; + } + + /** + * Simplify class names in the type. Please go to unit tests for usage examples. + * + * @param _type type described in the string format + * @return type with simplified class names + */ + public static String getSimpleTypeClasses(String _type) { + if (_type == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + Pattern compile = Pattern.compile("([^, <>?]+)"); + Matcher matcher = compile.matcher(_type); + while (matcher.find()) { + String match = matcher.group(); + matcher.appendReplacement(sb, Util.extractClassNameFromFqcn(match)); + } + matcher.appendTail(sb); + return sb.toString(); + } + + /** + * Get all classes that should be imported for the input type, this method works fine with generic types. + * Please go to unit tests for usage examples. + * + * @param _type type described in the string format + * @return set of classes required for imports + */ + public static Set getImportsForType(String _type) { + Set imports = new HashSet<>(); + if (!_type.contains("<")) { + if (!_type.startsWith("java.lang.") && _type.contains(".")) { + imports.add(_type); + } + return imports; + } + Pattern compile = Pattern.compile("([^, <>?]+)"); + Matcher matcher = compile.matcher(_type); + while (matcher.find()) { + String match = matcher.group(); + + if (!match.startsWith("java.lang.") && match.contains(".")) { + imports.add(match); + } + } + 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); + } + + /** + * Enum to define either the {@link ClassBuilderInfo} is for a CLASS or an INTERFACE. + * + * @author hypfvieh + * @since v3.0.1 - 2018-12-20 + */ + public enum ClassType { + INTERFACE, + CLASS; + } + +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassConstructor.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassConstructor.java new file mode 100644 index 00000000..99e9c321 --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassConstructor.java @@ -0,0 +1,112 @@ +package org.freedesktop.dbus.utils.generator.type; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Pojo which represents a class constructor. + * + * @author hypfvieh + * @since v3.0.1 - 2018-12-20 + */ +public class ClassConstructor implements ICodeGenerator { + private static final String CONSTRUCTOR_TEMPL = + """ + public %s(%s)%s { + %s + } + """; + + /** Map of arguments for the constructor. Key is argument name, value is argument type. */ + private final List arguments = new ArrayList<>(); + /** Map of arguments for the super-constructor. Key is argument name, value is argument type. */ + private final List superArguments = new ArrayList<>(); + + /** List of throws arguments. */ + private final List throwArguments = new ArrayList<>(); + + private final ClassBuilderInfo classBuilderInfo; + + private final String className; + + public ClassConstructor(ClassBuilderInfo _bldr, String _className) { + classBuilderInfo = _bldr; + className = _className; + } + + public List getThrowArguments() { + return throwArguments; + } + + public List getArguments() { + return arguments; + } + + public List getSuperArguments() { + return superArguments; + } + + public String getClassName() { + return className; + } + + @Override + public ClassBuilderInfo getClassBuilderInfo() { + return classBuilderInfo; + } + + @Override + public List generate(int _indentLevel) { + + List filteredSuperArguments = new ArrayList<>(getSuperArguments()); + filteredSuperArguments.removeIf(e -> getArguments().contains(e)); + String constructorArgs = ""; + + if (!filteredSuperArguments.isEmpty()) { + constructorArgs += filteredSuperArguments.stream().map(e -> e.asOneLineString(false)).collect(Collectors.joining(", ")); + if (!getArguments().isEmpty()) { + constructorArgs += ", "; + } + } + + if (!getArguments().isEmpty()) { + constructorArgs += argumentsAsString(); + } + + String throwArgs = getThrowArguments().isEmpty() ? "" : (" throws " + String.join(", ", getThrowArguments())); + + String assignments = ""; + + String prefix = getClassBuilderInfo().getArgumentPrefix(); + + if (!getSuperArguments().isEmpty()) { + assignments = getIndent(_indentLevel / 2) + "super(" + getSuperArguments().stream() + .map(e -> ClassBuilderInfo.maybePrefix(e.getName(), prefix)) + .collect(Collectors.joining(", ")) + ");" + System.lineSeparator(); + } + + if (!getArguments().isEmpty()) { + List assigns = new ArrayList<>(); + String innerIndent = getIndent(_indentLevel / 2); + for (MemberOrArgument e : getArguments()) { + assigns.add(innerIndent + "this." + e.getName() + " = " + ClassBuilderInfo.maybePrefix(e.getName(), prefix) + ";"); + } + assignments += String.join(System.lineSeparator(), assigns); + } + + return CONSTRUCTOR_TEMPL.formatted(getClassName(), constructorArgs, throwArgs, assignments) + .lines().map(l -> getIndent(_indentLevel) + l).toList(); + } + + public String argumentsAsString() { + return getArguments().stream().map(a -> a.asOneLineString(true)).collect(Collectors.joining(", ")); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [arguments=" + arguments + ", superArguments=" + superArguments + + ", throwArguments=" + throwArguments + "]"; + } + +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassMethod.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassMethod.java new file mode 100644 index 00000000..dd148908 --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ClassMethod.java @@ -0,0 +1,150 @@ +package org.freedesktop.dbus.utils.generator.type; + +import org.freedesktop.dbus.annotations.DBusBoundProperty; +import org.freedesktop.dbus.annotations.DBusMemberName; +import org.freedesktop.dbus.utils.Util; +import org.freedesktop.dbus.utils.generator.TypeConverter; +import org.freedesktop.dbus.utils.generator.type.AnnotationInfo.AnnotArgs; +import org.freedesktop.dbus.utils.generator.type.ClassBuilderInfo.ClassType; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Pojo which represents a class method. + * + * @author hypfvieh + * @since v3.0.1 - 2018-12-20 + */ +public class ClassMethod implements ICodeGenerator { + + private static final String METHOD_TEMPL = """ + %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<>(); + /** List of annotations for this method. */ + private final List annotations = new ArrayList<>(); + + private final ClassBuilderInfo classBuilderInfo; + + public ClassMethod(ClassBuilderInfo _bldr, String _name, String _returnType, boolean _finalMethod) { + this(_bldr, _name, _returnType, null, _finalMethod); + } + + public ClassMethod(ClassBuilderInfo _bldr, String _name, String _returnType, String _methodPrefix, boolean _finalMethod) { + name = _name; + returnType = _returnType; + finalMethod = _finalMethod; + methodPrefix = _methodPrefix; + classBuilderInfo = _bldr; + } + + public String getName() { + return name; + } + + public String getReturnType() { + return returnType; + } + + public String getMethodPrefix() { + return methodPrefix; + } + + public boolean isFinalMethod() { + return finalMethod; + } + + public List getArguments() { + return arguments; + } + + public List getAnnotations() { + return annotations; + } + + @Override + public ClassBuilderInfo getClassBuilderInfo() { + return classBuilderInfo; + } + + public String getMethodName() { + boolean hasPrefix = !Util.isBlank(getMethodPrefix()); + String methodName = reformatName(); + + if (hasPrefix) { + methodName = getMethodPrefix() + Util.upperCaseFirstChar(methodName); + } + + if (ClassBuilderInfo.isReservedMethodName(methodName)) { + methodName += "FromBus"; + } + + return methodName; + } + + private String reformatName() { + return Util.kebabToCamelCase(Util.snakeToCamelCase(getName())); + } + + @Override + public List generate(int _indentLevel) { + List result = new ArrayList<>(); + + List currentAnnotations = new ArrayList<>(getAnnotations()); + String methodName = getMethodName(); + + // 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)) { + + // add "name" definition if original name differs from reformatted name + // e.g. some-name != someName + if (!reformatName().equals(getName())) { + currentAnnotations.stream().filter(e -> e.getAnnotationClass() == DBusBoundProperty.class) + .forEach(e -> e.getAnnotationParams().put("name", getName())); + + } + + } else if (Util.isBlank(getMethodPrefix())) { + currentAnnotations.add(new AnnotationInfo(DBusMemberName.class, AnnotArgs.create().add(getName()))); + } + } + + if (!currentAnnotations.isEmpty()) { + result.addAll(currentAnnotations.stream().map(a -> getIndent(_indentLevel) + a.getAnnotationString()).toList()); + } + + String publicModifier = getClassBuilderInfo().getClassType() != ClassType.INTERFACE ? "public " : ""; + String mthReturnType = getReturnType() == null ? "void" + : TypeConverter.getProperJavaClass(getReturnType(), getClassBuilderInfo().getImports()); + String args = ""; + + if (!getArguments().isEmpty()) { + args += getArguments().stream() + .map(e -> e.asOneLineString(true)) + .collect(Collectors.joining(", ")); + } + + result.addAll(formatMethod(_indentLevel, publicModifier, mthReturnType, methodName, args)); + + return result; + } + + protected List formatMethod(int _indentLvl, String _modifier, String _returnType, String _methodName, String _args) { + return METHOD_TEMPL.formatted(_modifier, _returnType, _methodName, _args) + .lines().map(l -> getIndent(_indentLvl) + l).toList(); + } + +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/GetterMethod.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/GetterMethod.java new file mode 100644 index 00000000..a8eb4210 --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/GetterMethod.java @@ -0,0 +1,37 @@ +package org.freedesktop.dbus.utils.generator.type; + +import java.util.List; + +public class GetterMethod extends ClassMethod { + + private static final String GETTER_METHOD_TEMPL = """ + %s%s %s(%s) { + %s%s + } + """; + private final MemberOrArgument argument; + + public GetterMethod(ClassBuilderInfo _bldr, String _name, String _returnType) { + this(_bldr, _name, _returnType, null); + } + + public GetterMethod(ClassBuilderInfo _bldr, String _name, String _returnType, MemberOrArgument _arguments) { + super(_bldr, _name, _returnType, "boolean".equalsIgnoreCase(_returnType) ? "is" : "get", false); + this.argument = _arguments; + } + + @Override + protected List formatMethod(int _indentLvl, String _modifier, String _returnType, String _methodName, String _args) { + + if (argument == null) { + return super.formatMethod(_indentLvl, _modifier, _returnType, _methodName, _args); + } + + String content = String.format("return %s;", argument.getName()); + + return GETTER_METHOD_TEMPL.formatted(_modifier, _returnType, _methodName, _args, + getIndent(Math.min(1, _indentLvl - 1)), content) + .lines().map(l -> getIndent(_indentLvl) + l).toList(); + } + +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ICodeGenerator.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ICodeGenerator.java new file mode 100644 index 00000000..83b924bb --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/ICodeGenerator.java @@ -0,0 +1,16 @@ +package org.freedesktop.dbus.utils.generator.type; + +import java.util.List; + +public interface ICodeGenerator { + + String INDENT = " "; + + ClassBuilderInfo getClassBuilderInfo(); + + List generate(int _indentLevel); + + default String getIndent(int _indentLevel) { + return INDENT.repeat(_indentLevel); + } +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/MemberOrArgument.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/MemberOrArgument.java new file mode 100644 index 00000000..d6e3f18f --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/MemberOrArgument.java @@ -0,0 +1,132 @@ +package org.freedesktop.dbus.utils.generator.type; + +import org.freedesktop.dbus.utils.bin.IdentifierMangler; +import org.freedesktop.dbus.utils.generator.TypeConverter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Pojo which represents a class member/field or argument. + * + * @author hypfvieh + * @since v3.0.1 - 2018-12-20 + */ +public class MemberOrArgument implements ICodeGenerator { + + /** Name of member/field. */ + private final String name; + /** Type of member/field (e.g. String, int...). */ + private String type; + /** True to force this member to be final, false otherwise. */ + private final boolean finalArg; + /** 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 ClassBuilderInfo classBuilderInfo; + + public MemberOrArgument(ClassBuilderInfo _bldr, String _name, String _type, boolean _finalMember) { + // repair reserved words by adding 'Param' as appendix, and when start with _ too + name = (IdentifierMangler.isReservedWord(_name) || IdentifierMangler.isReservedWord(_name.replaceFirst("^_(.+)", "$1"))) ? _name + "param" : _name; + type = _type; + finalArg = _finalMember; + classBuilderInfo = _bldr; + } + + public MemberOrArgument(ClassBuilderInfo _bldr, String _name, String _type) { + this(_bldr, _name, _type, false); + } + + public List getAnnotations() { + return annotations; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public void setType(String _type) { + type = _type; + } + + public boolean isFinalArg() { + return finalArg; + } + + public List getGenerics() { + return generics; + } + + public String getFullType(Set _allImports) { + StringBuilder sb = new StringBuilder(); + sb.append(TypeConverter.getProperJavaClass(getType(), _allImports)); + + if (!getGenerics().isEmpty()) { + sb.append("<") + .append(getGenerics().stream().map(c -> TypeConverter.convertJavaType(c, false)).collect(Collectors.joining(", "))) + .append(">"); + } + + return sb.toString(); + } + + @Override + public ClassBuilderInfo getClassBuilderInfo() { + return classBuilderInfo; + } + + @Override + public List generate(int _indentLevel) { + List result = new ArrayList<>(); + String memberType = TypeConverter.getProperJavaClass(getType(), getClassBuilderInfo().getImports()); + + if (!getGenerics().isEmpty()) { + memberType += "<" + getGenerics().stream().map(c -> TypeConverter.convertJavaType(c, false)).collect(Collectors.joining(", ")) + ">"; + } + + GetterMethod getterMethod = new GetterMethod(getClassBuilderInfo(), getName(), memberType, this); + + getClassBuilderInfo().getMethods().add(getterMethod); + if (!isFinalArg()) { + getClassBuilderInfo().getMethods().add(new SetterMethod(getClassBuilderInfo(), getName(), memberType)); + } + + return result; + } + + public String asOneLineString(boolean _includeAnnotations) { + StringBuilder sb = new StringBuilder(); + + if (isFinalArg()) { + sb.append("final "); + } + + if (_includeAnnotations && !getAnnotations().isEmpty()) { + sb.append(String.join(" ", getAnnotations().stream().map(e -> e.getAnnotationString()).toList())) + .append(" "); + } + + sb.append(getFullType(getClassBuilderInfo().getImports())); + + sb.append(" "); + + sb.append(ClassBuilderInfo.maybePrefix(getName(), getClassBuilderInfo().getArgumentPrefix())); + + return sb.toString(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [name=" + name + ", type=" + type + ", finalArg=" + finalArg + ", generics=" + + generics + ", annotations=" + annotations + "]"; + } + +} diff --git a/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/SetterMethod.java b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/SetterMethod.java new file mode 100644 index 00000000..52b91fa4 --- /dev/null +++ b/dbus-java-utils/src/main/java/org/freedesktop/dbus/utils/generator/type/SetterMethod.java @@ -0,0 +1,31 @@ +package org.freedesktop.dbus.utils.generator.type; + +import java.util.List; + +public class SetterMethod extends ClassMethod { + + private static final String SETTER_METHOD_TEMPL = """ + %s%s %s(%s) { + %s%s + } + """; + + public SetterMethod(ClassBuilderInfo _bldr, String _name, String _setterType) { + super(_bldr, _name, "void", "set", false); + getArguments().add(new MemberOrArgument(_bldr, _name, _setterType)); + } + + @Override + protected List formatMethod(int _indentLvl, String _modifier, String _returnType, String _methodName, String _args) { + + if (getArguments() == null || getArguments().isEmpty()) { + return super.formatMethod(_indentLvl, _modifier, _returnType, _methodName, _args); + } + + String content = String.format("this.%s = %s;", getArguments().getFirst().getName(), getArguments().getFirst().getName()); + + return SETTER_METHOD_TEMPL.formatted(_modifier, _returnType, _methodName, _args, + getIndent(Math.min(1, _indentLvl - 1)), content) + .lines().map(l -> getIndent(_indentLvl) + l).toList(); + } +} diff --git a/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfoTest.java b/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfoTest.java index b62e4a91..2fa7fa8d 100644 --- a/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfoTest.java +++ b/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/ClassBuilderInfoTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import org.freedesktop.dbus.utils.generator.type.ClassBuilderInfo; import org.junit.jupiter.api.Test; import java.util.Set; 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 72bdacdc..004dac3d 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 @@ -1,5 +1,6 @@ package org.freedesktop.dbus.utils.generator; +import static org.junit.jupiter.api.AssertionFailureBuilder.assertionFailure; import static org.junit.jupiter.api.Assertions.*; import org.freedesktop.dbus.annotations.DBusInterfaceName; @@ -10,6 +11,8 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.File; +import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Stream; @@ -81,8 +84,11 @@ void testHandleKebabCase() throws Exception { String clzContent = analyze.get(analyze.keySet().iterator().next()); - assertTrue(clzContent.contains("@DBusBoundProperty(name = \"power-saver-enabled\")")); - assertTrue(clzContent.contains("boolean isPowerSaverEnabled();")); + assertLineEquals(12, clzContent, " @DBusBoundProperty(name = \"power-saver-enabled\")"); + assertLineEquals(13, clzContent, " boolean isPowerSaverEnabled();"); + + assertLineEquals(15, clzContent, " @DBusBoundProperty"); + assertLineEquals(16, clzContent, " UInt32 getVersion();"); } @Test @@ -96,8 +102,25 @@ void testHandleReservedMethodNames() throws Exception { String clzContent = analyze.get(analyze.keySet().iterator().next()); - assertTrue(clzContent.contains("@DBusMemberName(\"Serial\")")); - assertTrue(clzContent.contains("public UInt32 getSerialFromBus()")); + assertLineEquals(73, clzContent, " public UInt32 getSerialFromBus() {"); + } + + @Test + void testHandleReservedMethodNames2() throws Exception { + InterfaceCodeGenerator ci2 = loadDBusXmlFile(true, + new File("src/test/resources/CreateInterface/sample_reserved_names.xml"), + "/", "org.example.Reserved"); + Map analyze = ci2.analyze(true); + + assertEquals(1, analyze.size()); + + String clzContent = analyze.get(analyze.keySet().iterator().next()); + + assertLineEquals(12, clzContent, " @DBusMemberName(\"getWireData\")"); + assertLineEquals(13, clzContent, " int getWireDataFromBus();"); + + assertLineNotEquals(23, clzContent, " @DBusMemberName(\"serial\")"); + assertLineEquals(24, clzContent, " public DBusPath getSerialFromBus() {"); } @Test @@ -115,9 +138,9 @@ void testHandleStructSignals() throws Exception { .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() {")); + assertLineEquals(101, primaryFile, " public ShortcutsChanged(String path, DBusPath sessionHandle, List shortcuts) throws DBusException {"); + assertLineEquals(99, primaryFile, " private final List shortcuts;"); + assertLineEquals(111, primaryFile, " public List getShortcuts() {"); String secondaryFile = analyze.entrySet().stream() .filter(e -> e.getKey().getName().equals("ShortcutsChangedShortcutsStruct.java")) @@ -125,9 +148,10 @@ void testHandleStructSignals() throws Exception { .orElseThrow() .getValue(); - assertTrue(secondaryFile.contains("private final String member0;")); - assertTrue(secondaryFile.contains("private final Map> member1;")); - + assertLineEquals(11, secondaryFile, " @Position(0)"); + assertLineEquals(12, secondaryFile, " private final String member0;"); + assertLineEquals(13, secondaryFile, " @Position(1)"); + assertLineEquals(14, secondaryFile, " private final Map> member1;"); } @Test @@ -141,7 +165,7 @@ void testIssue306() throws Exception { String clzContent = analyze.get(analyze.keySet().iterator().next()); - assertTrue(clzContent.contains("@DBusBoundProperty(type = PropertySupportedOptionsType.class)")); + assertLineEquals(22, clzContent, " @DBusBoundProperty(type = PropertySupportedOptionsType.class)"); } @Test @@ -154,10 +178,10 @@ void testCreateSampleStructArgs() throws Exception { String clzContent = analyze.get(new File("org", "ExampleMethodExampleArgStruct.java")); - assertTrue(clzContent.contains("@Position(0)"), "Position annotation expected"); - assertTrue(clzContent.contains("private final List member0;"), "Final List member expected"); - assertTrue(clzContent.contains("public ExampleMethodExampleArgStruct(List member0)"), "Constructor using List expected"); - assertTrue(clzContent.contains("public List getMember0()"), "Getter for Member of type List expected"); + assertLineEquals(10, clzContent, " @Position(0)"); + assertLineEquals(11, clzContent, " private final List member0;"); + assertLineEquals(13, clzContent, " public ExampleMethodExampleArgStruct(List member0) {"); + assertLineEquals(17, clzContent, " public List getMember0() {"); } @Test @@ -206,4 +230,42 @@ static Stream createFindGenericNameData() { Arguments.of("ABCDEF -> G", Set.of("A", "B", "C", "D", "E", "F"), "G") ); } + + static void assertLineEquals(int _lineNo, String _lines, String _compare) { + assertLineEquals(true, _lineNo, _lines, _compare); + } + + static void assertLineNotEquals(int _lineNo, String _lines, String _compare) { + assertLineEquals(false, _lineNo, _lines, _compare); + } + + /** + * Assert that the specified line is equal to the compare value. + * @param _notContains whether the line should or should not contain the compare value + * @param _lineNo line to compare (zero based) + * @param _lines string containing lines (will be splitted by line feed) + * @param _compare compare value + */ + private static void assertLineEquals(boolean _notContains, int _lineNo, String _lines, String _compare) { + assertNotNull(_lines, "Lines required"); + assertNotNull(_compare, "Compare line required"); + + List list = Arrays.asList(_lines.split("\n")); + assertFalse(list.isEmpty(), "No lines to compare with"); + + if (_lineNo >= list.size()) { + fail("LineNo " + _lineNo + " is bigger than the available lines " + list.size()); + } else if (_lineNo < 0) { + fail("LineNo must be >= 0"); + } + + if (_notContains != _compare.equals(list.get(_lineNo))) { + assertionFailure() + .message("Line " + _lineNo + " does not match") + .expected(_compare) + .actual(list.get(_lineNo)) + .buildAndThrow(); + } + + } } diff --git a/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/StructTreeBuilderTest.java b/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/StructTreeBuilderTest.java index 97c674f7..cc8169a5 100644 --- a/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/StructTreeBuilderTest.java +++ b/dbus-java-utils/src/test/java/org/freedesktop/dbus/utils/generator/StructTreeBuilderTest.java @@ -4,6 +4,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import org.freedesktop.dbus.exceptions.DBusException; +import org.freedesktop.dbus.utils.generator.type.ClassBuilderInfo; import org.junit.jupiter.api.Test; import java.util.ArrayList; diff --git a/dbus-java-utils/src/test/resources/CreateInterface/sample_reserved_names.xml b/dbus-java-utils/src/test/resources/CreateInterface/sample_reserved_names.xml new file mode 100644 index 00000000..fc211755 --- /dev/null +++ b/dbus-java-utils/src/test/resources/CreateInterface/sample_reserved_names.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file