JCombiner is a framework to generate combinations of collections in Java. It uses:
- JDK 11
- JPMS (Java Platform Module System)
- Gradle
- JUnit 5
- Mockito
- JaCoCo
./gradlew clean build
- Create some class which holds collection of elements, let's say the following
People
class:
public class People {
private List<String> people;
public People() {
this.people = new ArrayList<>();
}
public void addPerson(final String name) {
this.people.add(Objects.requireNonNull(name));
}
}
- Add the
CombinationProperty
annotation to the collection attribute with the desired combination size, let's saysize=3
:
public class People {
@CombinationProperty(size = 3)
private List<String> people;
public People() {
this.people = new ArrayList<>();
}
public void addPerson(final String name) {
this.people.add(Objects.requireNonNull(name));
}
}
- Create the target class to hold the combinations result. The target class should have a
Combinations
field for each mappedCombinationProperty
attribute from the original object. TheCombinations
class is parameterized and it should have the same type argument from the mapped collection attribute, in this case,String
:
public class PeopleCombinations {
private Combinations<String> people;
@Override
public String toString() {
return Objects.toString(people);
}
}
By default, if the CombinationProperty
annotation has no name
element defined, the Combinations
attribute must have the same name from the original field. Otherwise, the Combinations
field name must match the name
defined in the CombinationProperty
annotation. In case none of these situations occur, an exception will be throw by the framework.
- Get a reference to the JCombiner service. The
JCombiner
service is the entry point to build field combinations of an object. This framework uses Java 9 modules, so one way to get a reference to it is to use the ServiceLoader class to load a specific service:
final ServiceLoader<JCombiner> jCombiners = ServiceLoader.load(JCombiner.class);
- Use the
JCombiner
service to build the combinations for each mapped field from the source object:
public class Main {
public static void main(String[] args) {
final var people = new People();
people.addPerson("John");
people.addPerson("Paul");
people.addPerson("Patrick");
people.addPerson("Joe");
people.addPerson("Thomas");
people.addPerson("Emma");
final ServiceLoader<JCombiner> jCombiners = ServiceLoader.load(JCombiner.class);
jCombiners.forEach(jCombiner -> {
final var peopleCombinations = (PeopleCombinations) jCombiner.parseCombinations(people, PeopleCombinations.class);
System.out.println(peopleCombinations);
});
}
}
- Running the above code, here is the output for the mapped
people
field with a three size combination (@CombinationProperty(size = 3)
):
[[John, Paul, Patrick], [John, Paul, Joe], [John, Paul, Thomas], [John, Paul, Emma], [John, Patrick, Joe], [John, Patrick, Thomas], [John, Patrick, Emma], [John, Joe, Thomas], [John, Joe, Emma], [John, Thomas, Emma], [Paul, Patrick, Joe], [Paul, Patrick, Thomas], [Paul, Patrick, Emma], [Paul, Joe, Thomas], [Paul, Joe, Emma], [Paul, Thomas, Emma], [Patrick, Joe, Thomas], [Patrick, Joe, Emma], [Patrick, Thomas, Emma], [Joe, Thomas, Emma]]
It is possible to create custom annotations annotated with the @CombinationProperty
annotation and then reuse those
annotations through collection fields.
Let's suppose the following custom annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@CombinationProperty(size = 3)
public @interface ThreeSizeCombination {
}
Using the example from the previous section, it is possible to change the People
class to use the custom
ThreeSizeCombination
annotation:
public class People {
@ThreeSizeCombination
private List<String> people;
public People() {
this.people = new ArrayList<>();
}
public void addPerson(final String name) {
this.people.add(Objects.requireNonNull(name));
}
}
Using the JCombiner
service to build the combinations for the People
class (as explained here),
the output should be the same:
[[John, Paul, Patrick], [John, Paul, Joe], [John, Paul, Thomas], [John, Paul, Emma], [John, Patrick, Joe], [John, Patrick, Thomas], [John, Patrick, Emma], [John, Joe, Thomas], [John, Joe, Emma], [John, Thomas, Emma], [Paul, Patrick, Joe], [Paul, Patrick, Thomas], [Paul, Patrick, Emma], [Paul, Joe, Thomas], [Paul, Joe, Emma], [Paul, Thomas, Emma], [Patrick, Joe, Thomas], [Patrick, Joe, Emma], [Patrick, Thomas, Emma], [Joe, Thomas, Emma]]
More examples can be found on the client module.