RangeCondition implements Predicate<Wrapper<?>> { static <X extends Number & Comparable<X>> RangeCondition<Ratio> relativeExact(final Function<Wrapper<?>, X> part, final Function<Wrapper<?>, X> sum, final Ratio minimum, final Ratio maximum) { final Domain<Ratio> allowedValues = AbstractRelativeRangeCondition.RELATIVE_DOMAIN; if (!allowedValues.test(minimum)) { throw new IllegalArgumentException("Minimum " + minimum + " does not fit " + allowedValues); } else if (!allowedValues.test(maximum)) { throw new IllegalArgumentException("Maximum " + maximum + " does not fit " + allowedValues); } else if (minimum.compareTo(maximum) > 0) { throw new IllegalArgumentException("Minimum " + minimum + " is over maximum " + maximum); } final Function<Wrapper<?>, Ratio> value = w -> getValueAccessor(w, part, sum); return new RangeCondition<>(value, v -> v.compareTo(minimum) >= 0 && v.compareTo(maximum) <= 0, "Relative range: <" + minimum + "; " + maximum + ">"); } private  RangeCondition(final Function<Wrapper<?>, T> accessor, final Predicate<T> acceptor,
            final String toString); @Override boolean test(final Wrapper<?> wrapper); @Override String toString();  }
@Test void relativeExact() { final RangeCondition<Ratio> c = RangeCondition.relativeExact(Wrapper::getRemainingTermInMonths, Wrapper::getOriginalTermInMonths, Ratio.fromPercentage(15), Ratio.fromPercentage(15)); final Wrapper<?> w = mock(Wrapper.class); when(w.getOriginalTermInMonths()).thenReturn(100); when(w.getRemainingTermInMonths()).thenReturn(15); assertThat(c).accepts(w); when(w.getRemainingTermInMonths()).thenReturn(14); assertThat(c).rejects(w); when(w.getRemainingTermInMonths()).thenReturn(16); assertThat(c).rejects(w); }