Git Product home page Git Product logo

Comments (8)

codecholeric avatar codecholeric commented on September 25, 2024

You're right, the predefined API doesn't support this case at the moment. However, you can add it yourself:

@Test
void serviceAnnotation() {
  JavaClasses classes = new ClassFileImporter().importPackagesOf(DummyBean.class);

  classes()
      .that().areAssignableTo(DummyBean.class)
      .should(beMetaAnnotatedWith(Service.class))
      .check(classes);
}

private ArchCondition<JavaClass> beMetaAnnotatedWith(
    final Class<? extends Annotation> annotationType) {

  final String annotationName = annotationType.getSimpleName();
  return new ArchCondition<JavaClass>("be meta-annotated with @" + annotationName) {
    @Override
    public void check(JavaClass javaClass, ConditionEvents events) {
      if (!isMetaAnnotated(javaClass)) {
        events.add(SimpleConditionEvent.violated(javaClass,
            String.format("Class %s is not meta-annotated with @%s",
                javaClass.getName(), annotationName)));
      }
    }

    private boolean isMetaAnnotated(JavaClass javaClass) {
      if (javaClass.isAnnotatedWith(annotationType)) {
        return true;
      }

      for (JavaClass metaAnnotationType : getMetaAnnotationTypes(javaClass.getAnnotations())) {
        if (metaAnnotationType.isAnnotatedWith(annotationType)) {
          return true;
        }
      }
      return false;
    }

    private Set<JavaClass> getMetaAnnotationTypes(Set<JavaAnnotation> annotations) {
      Set<JavaClass> types = new HashSet<>();
      for (JavaAnnotation annotation : annotations) {
        types.add(annotation.getType());
        types.addAll(getMetaAnnotationTypes(annotation.getType().getAnnotations()));
      }
      return types;
    }
  };
}

It's a little verbose, but it should do, what you want, if I've correctly understood your issue. Does this help you?

from archunit.

codecholeric avatar codecholeric commented on September 25, 2024

Actually I was just pondering, this can be written a little shorter, by using the syntax should().beAnnotatedWith(predicate) (this time in Java 8):

@Test
void serviceAnnotation() {
  JavaClasses classes = new ClassFileImporter().importPackagesOf(DummyBean.class);

  classes()
      .that().areAssignableTo(DummyBean.class)
      .should().beAnnotatedWith(metaAnnotation(Service.class))
      .check(classes);
}

private DescribedPredicate<JavaAnnotation> metaAnnotation(
    final Class<? extends Annotation> annotationType) {

  return new DescribedPredicate<JavaAnnotation>("meta-annotation @" + annotationType.getSimpleName()) {
    @Override
    public boolean apply(JavaAnnotation annotation) {
      return annotation.getType().isEquivalentTo(annotationType) ||
          getMetaAnnotations(annotation).stream()
              .anyMatch(type -> type.isAnnotatedWith(annotationType));
    }

    private Set<JavaClass> getMetaAnnotations(JavaAnnotation annotation) {
      Set<JavaClass> types = new HashSet<>(singleton(annotation.getType()));
      annotation.getType().getAnnotations().stream()
          .map(this::getMetaAnnotations)
          .forEach(types::addAll);
      return types;
    }
  };
}

Should do the same thing, as the code above, i.e. accept all types that are directly annotated with @Service, or meta-annotated with @Service...

from archunit.

rweisleder avatar rweisleder commented on September 25, 2024

Yep, that's just right for my requirement.

Should this be part of ArchUnit, either as API or as an "how to extend"-example in the user guide?

from archunit.

codecholeric avatar codecholeric commented on September 25, 2024

Glad to hear that it covers your case 😃
Yes, I think this would be a good addition to the predefined API, so I'll keep this issue open and propose to extend the syntax consistently to annotatedWith:

classes().

  • that().areMetaAnnotatedWith(Class<? extends Annotation> clazz)
  • that().areMetaAnnotatedWith(String className)
  • that().areMetaAnnotatedWith(DescribedPredicate<JavaAnnotation> predicate)
  • should().beMetaAnnotatedWith(Class<? extends Annotation> clazz)
  • should().beMetaAnnotatedWith(String className)
  • should().beMetaAnnotatedWith(DescribedPredicate<JavaAnnotation> predicate)

and the respective negations. I'll see if someone wants to contribute a PR, otherwise I'll do it myself, as soon as I get around to it (which might be a little while though, since there are so many older issues in the pipeline).

from archunit.

rweisleder avatar rweisleder commented on September 25, 2024

Since I'm able to build the project now (thanks @codecholeric), I will start to work on this issue.

from archunit.

codecholeric avatar codecholeric commented on September 25, 2024

Cool, thanks a lot 😃 Let me know, if you need any support!

from archunit.

rweisleder avatar rweisleder commented on September 25, 2024

While implementing tests for this one, I found an issue relating to class resolving. If an annotation type is not imported directly, the information about meta-annotations are not present.

Example:

@Deprecated
public class ExampleTest {

  @Test
  public void demo() {
    ArchRule rule = classes()
        .that().haveSimpleName("ExampleTest")
        .should().beAnnotatedWith(metaAnnotation(Retention.class));

    ClassFileImporter importer = new ClassFileImporter();

    // this check succeeds
    JavaClasses allClasses = importer.importClasses(ExampleTest.class, Deprecated.class);
    rule.check(allClasses);

    // this check fails, but should succeed
    JavaClasses myClasses = importer.importClasses(ExampleTest.class);
    rule.check(myClasses);
  }

  private DescribedPredicate<? super JavaAnnotation> metaAnnotation(final Class<? extends Annotation> type) {
    return new DescribedPredicate<JavaAnnotation>("meta-annotation @" + type.getSimpleName()) {
      @Override
      public boolean apply(JavaAnnotation input) {
        return input.getType().isAnnotatedWith(type);
      }
    };
  }

}

@codecholeric any ideas how to proceed?

from archunit.

codecholeric avatar codecholeric commented on September 25, 2024

I don't think this is an issue, but a known limitation. Imagine this structure:

foo
 |-- bar/MyAnnotation.class
 |-- baz/AnnotatedClass.class

If you import via new ClassFileImporter().importPath("/foo/baz"), then ArchUnit will find that AnnotatedClass is annotated with MyAnnotation from the byte code. However, all further information about MyAnnotation (like meta-annotation) is not available. And there is simply no way, to know anything about MyAnnotation, if it was not imported as well.
On the other hand, this is something the user of ArchUnit has to take care of. All classes that are to be considered within tests have to be imported. So in my opinion, this is no obstacle for you, simply import all necessary classes within tests, including meta-annotated types to test.

One thing you could argue about though, is if the ClassResolverFromClasspath should be able to correctly import meta-annotations, since it does not do this at the moment (I would expect, that if resolveMissingDependenciesFromClasspath=true, the above test should work, since ArchUnit should resolve Deprecated including all meta-annotations from the classpath, however, meta-annotations are still missing).

I'll think about this, but I think this is a separate issue, independently of syntax extensions, since those work fine if all necessary classes are imported.

from archunit.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.