SCM
SCM

Reputation: 85

Check if a field is initialized with ArchUnit

I have a class that looks like this:

public class A {

   private B b1 = new B();

   private C c = new C();

   private B b2;

}

I want to write an ArchUnit test that finds all the fields in class A that are of type B and that are initialized. So in this case, just field 'b1'.

I wrote a test like this, but can't finish it. Is this even possible?

 @ArchTest
  void dontInitialize(JavaClasses classesToTest) {
    noFields().that().haveRawType(DescribedPredicate.describe("that is a B object", type -> {
      return type.isAssignableTo(B.class);
    })).should(new ArchCondition<JavaField>("") {
      @Override
      public void check(JavaField javaField, ConditionEvents conditionEvents) {
        // ???
      }
    });
  }

Upvotes: 1

Views: 202

Answers (1)

Manfred
Manfred

Reputation: 3142

At the byte code level, field initialization is moved into the constructor. For your example class A:

  private B b1;
    descriptor: LB;
    flags: (0x0002) ACC_PRIVATE

  private C c;
    descriptor: LC;
    flags: (0x0002) ACC_PRIVATE

  private B b2;
    descriptor: LB;
    flags: (0x0002) ACC_PRIVATE

  public A();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #7                  // class B
         8: dup
         9: invokespecial #9                  // Method B."<init>":()V
        12: putfield      #10                 // Field b1:LB;
        15: aload_0
        16: new           #16                 // class C
        19: dup
        20: invokespecial #18                 // Method C."<init>":()V
        23: putfield      #19                 // Field c:LC;
        26: return

So you can have an ArchRule that tests for setting accesses from a constructor, e.g.:

fields().should(be(describe("set in a constructor", field ->
    field.getAccessesToSelf().stream().anyMatch(access ->
        access.getOwner().isConstructor() &&
        access.getAccessType() == JavaFieldAccess.AccessType.SET
    )
)));

using the following static imports:

import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.lang.conditions.ArchConditions.be;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.fields;

This simple rule can have false negatives when multiple constructors are involved, e.g.

class A {
    int i1;
    int i2;

    A() {
        i1 = 0;
    }

    A(int i2) {
        this.i2 = i2;
    }
}

but you can probably work out a smarter rule based on this idea.

Upvotes: 2

Related Questions