Fork me on GitHub

PowerUnit Extension Matchers

This library provides support to generate matcher for Pojo and the DSL like feature of hamcrest.

Generation of Matcher

A new annotation @ProvideMatchers is available, to be used at class level. This marks the class to be processed. Then, a Matchers class will be generated by the annotation processor.

For instance, for the following class :

/**
 * @author borettim
 *
 */
@ProvideMatchers
public class PojoShort {
        public String msg1;

}

Then, the following class will be generated :

@javax.annotation.Generated("ch.powerunit.extensions.matchers.provideprocessor.ProvidesMatchersAnnotationsProcessor")
public final class SampleMatchers {

  private SampleMatchers() {}

  // Method getField1 for field field1 of java.lang.String
  private static class Field1Matcher extends org.hamcrest.FeatureMatcher<Sample,java.lang.String> {

    public Field1Matcher(org.hamcrest.Matcher<? super java.lang.String> matcher) {
      super(matcher,"field1","field1");
  }

    protected java.lang.String featureValueOf(Sample actual) {
      return actual.getField1();
    }

  }


  /**
   * DSL interface for matcher on {@link ch.powerunit.extensions.matchers.samples.Sample Sample}.
   */
  public static interface SampleMatcher extends org.hamcrest.Matcher<Sample> {
    /**
     * Add a validation on the field field1.
     *
     * {@link ch.powerunit.extensions.matchers.samples.Sample#getField1() This field is accessed by using this approach}.
     *
     * @param matcher a Matcher on the field.
     * @return the DSL to continue the construction of the matcher.
     */
    org.hamcrest.Matcher<Sample> field1(org.hamcrest.Matcher<? super java.lang.String> matcher);
    /**
     * Add a validation on the field field1.
     *
     * {@link ch.powerunit.extensions.matchers.samples.Sample#getField1() This field is accessed by using this approach}.
     *
     * @param value an expected value for the field, which will be compared using the is matcher.
     * @return the DSL to continue the construction of the matcher.
     */
    org.hamcrest.Matcher<Sample> field1(java.lang.String value);
  }

  private static class SampleMatcherImpl extends org.hamcrest.TypeSafeDiagnosingMatcher<Sample> implements SampleMatcher {
    private Field1Matcher field1 = new Field1Matcher(org.hamcrest.Matchers.anything());

    @Override
    public org.hamcrest.Matcher<Sample> field1(org.hamcrest.Matcher<? super java.lang.String> matcher) {
      field1= new Field1Matcher(matcher);
      return this;
    }

    @Override
    public org.hamcrest.Matcher<Sample> field1(java.lang.String value) {
      return field1(org.hamcrest.Matchers.is(value));
    }

    @Override
    protected boolean matchesSafely(Sample actual, org.hamcrest.Description mismatchDescription) {
      boolean result=true;
      if(!field1.matches(actual)) {
        mismatchDescription.appendText("[");
        field1.describeMismatch(actual,mismatchDescription);
        mismatchDescription.appendText("]\n");
        result=false;
      }
      return result;
    }

    @Override
    public void describeTo(org.hamcrest.Description description) {
        description.appendText("an instance of ch.powerunit.extensions.matchers.samples.Sample with\n");
        description.appendText("[");
        description.appendDescriptionOf(field1);
        description.appendText("]\n");
    }
  }

  /**
   * Start a DSL matcher for the {@link ch.powerunit.extensions.matchers.samples.Sample Sample}.
   * 
   * @return the DSL matcher.
   */
  @org.hamcrest.Factory
  public static SampleMatcher sampleWith() {
    return new SampleMatcherImpl();
  }

}

A new method sampleWith is available, to start the building of the matcher. The field can be specified directly by his name (sampleWith().field1("v")). In case there is several fields, the various methods of the interface return the same interface, to be able to set the various field.

DSL Generation

The static method annotated with @Factory (annotation from hamcrest) can also be processed by another processor. This require to pass the parameter ch.powerunit.extensions.matchers.factoryprocessor.FactoryAnnotationsProcessor.targets with a valid value (see below for the exact syntax). It will create interface, with default method using the various annotated method. Assuming, we have only the previous method annotated with @Factory and the parameter is .*:ch.powerunit.extensions.matchers.samples.AllMatchers then a class AllMatchers will be created in the following way :

/**
 * Factories ...
 */
@javax.annotation.Generated("ch.powerunit.extensions.matchers.factoryprocessor.FactoryAnnotationsProcessor")
public interface AllMatchers {

  public static AllMatchers DSL = new AllMatchers() {};

  // sampleWith
  default ch.powerunit.extensions.matchers.samples.SampleMatchers.SampleMatcher sampleWith() {
    return ch.powerunit.extensions.matchers.samples.SampleMatchers.sampleWith();
  }

}

This DSL can be used in two ways :

  • First, by implementing the interface ; In this case all the method of the DSL are available to the implementing class. This method can be used for instance from another library that use the generated class, but may not be used from the same library (due to the order of the annotation processing at compile time).
  • Second, by using the DSL static field, which provide access to the DSL.

The parameter ch.powerunit.extensions.matchers.factoryprocessor.FactoryAnnotationsProcessor.targets define the mapping between the classes containing the method annotated with @Factory and the expected DSL classes. The syntax of this parameter is the following :

  • Split the parameter on the ; ; Each block is a mapping to one single DSL class.
  • Each block is then splitted on the : ; The left side is a list (comma separated) of regular expression and the right side is the target DSL class.