Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Collection the methods of all interfaces #1117

Closed
commodis opened this issue May 31, 2023 · 5 comments
Closed

Collection the methods of all interfaces #1117

commodis opened this issue May 31, 2023 · 5 comments

Comments

@commodis
Copy link

I try to find all unused methods.

A big set to be ignored methods are those, which have to be implemented by an interface or an abstract method.

My attempt was to:

  1. check all interfaces and superclasses of the owner of a method
  2. collect all their methods
  3. match against the method

I saw a potential implementation for superclasses in #982;
but my test seems to fail while gathering all methods of interfaces.

private boolean isInheritedInterfaceMethod(JavaMethod method) {
    final var methodName = method.getName();
    final var owner = method.getOwner();

    final var interfaces = owner.getAllRawInterfaces();
    final var interfaceMethods = interfaces.stream()
                        .map(JavaClass::getAllMethods)
                        .flatMap(Set::stream)
                        .toList();

    return interfaceMethods.stream()
        .anyMatch(interfaceMethod -> isSameMethod(method, interfaceMethod));
}

Debugging this code I noticed that an interface is of type JavaClass and thus automatically the following statements are all false:

  • isAnnotation: false,
  • isAnonymousClass: false,
  • isEnum: false,
  • isMemberClass: false,
  • isRecord: false,
  • but most importantly isInterface: false.

Running either getMethods or getAllMethods on an interface always returns an empty set.

Am I misusing the provided APIs?
If yes, can somebody point me into the correct usage?
If no, is this a bug or intentional?

Thanks for reading!

@hankem
Copy link
Member

hankem commented May 31, 2023

If the JavaClass corresponding to an interface (or also class) isFullyImported(), then isInterface(), getMethods() & getAllMethods() should work as you expect, which you can verify with the following test:

@Test
void properties_of_fully_imported_interface() {
    interface Parent {
        void parentMethod();
    }
    interface Child extends Parent {
        void childMethod();
    }

    JavaClass childClass = new ClassFileImporter().importClasses(Child.class, Parent.class).get(Child.class);

    assertThat(childClass.isFullyImported()).isTrue();
    assertThat(childClass.isInterface()).isTrue();
    assertThat(childClass.getMethods()).extracting(JavaMethod::getName).containsExactly("childMethod");
    assertThat(childClass.getAllMethods()).extracting(JavaMethod::getName).containsExactly("childMethod", "parentMethod");
}

I assume that you ended up with a JavaClass that is not fully imported. (Classes that are only transitively imported are not necessarily fully imported.) Those behave as you describe – but not because it is an interface, but because the information was not made available. You can verify this with the following test:

@Test
void properties_of_not_fully_imported_classe() {
    interface Dependency {
        void method();
    }
    class DependencyHolder {
        Dependency dependency;
    }

    ArchConfiguration.get().setResolveMissingDependenciesFromClassPath(false);
    JavaClass dependencyClass = new ClassFileImporter().importClass(DependencyHolder.class).getField("dependency").getRawType();

    assertThat(dependencyClass.isFullyImported()).isFalse();
    assertThat(dependencyClass.isInterface()).isFalse();   // We just don't know...
    assertThat(dependencyClass.getMethods()).isEmpty();    // We just don't know...
    assertThat(dependencyClass.getAllMethods()).isEmpty(); // We just don't know...
}

@hankem
Copy link
Member

hankem commented May 31, 2023

Regarding your attempt to find unused methods, you can probably build on #131 (comment).

@commodis
Copy link
Author

I see 🤔 thanks for the clarification on how that works! As you expected it returns false on an interface of an external dependency, which is unknown to the project. It did not cross my mind, that the dependency was external.

To resolve this, do I have to add the necessary dependencies to the ArchUnit-Project? Or is there some other workaround?

Also thanks for the pointer to the project!

@codecholeric
Copy link
Collaborator

I don't know what problem you ha(d/ve) exactly, but generally if you want to find inward dependencies (usages of your class/method), then you have to import all the other sources targeting your class/method as well or ArchUnit has no way to build up the graph and know about those. In the end, if you would e.g. take some library class from a popular library, then there would be tens of thousands of usages somewhere, but impossible to know from a local scope. Only if you import all those other classes as well ArchUnit can know about them.

If it's outward dependencies, this can partially be done automatically by configuring the resolution behavior

@commodis
Copy link
Author

commodis commented Nov 2, 2023

Thanks - I have never read that section. It was very insightful 👍

I assume that you ended up with a JavaClass that is not fully imported. (Classes that are only transitively imported are not necessarily fully imported.) Those behave as you describe – but not because it is an interface, but because the information was not made available.

Something which might help people in the future,
would be to add a message in case the expected class was not fully imported (like a warning)

But this would be a different issue thus I am closing it because I resolved it with that remark.

Thanks!

@commodis commodis closed this as completed Nov 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants