A handy DSL-ish helper to make the comparison more readable.
Zeliba provides a fluent API to write a comparison (for Comparable<T>
) and does other checks.
Inspired by AssertJ, kotlin, ZE FISH
Zeliba main points are the following:
- Provide a fluent API to write a comparison (for
Comparable<T>
) - Make
if
-checks better align with English grammar - Provide pattern matching for
Java 8
Java doesn't support operator overloading, you can’t write something like a > b
for objects,
as an alternative you can use Comparable<T>
. It makes its job, but it is not very convenient to use.
Look a.compareTo(b) > ??
.
Every time you need to make small calculations in your head. It’s better (from readability POV)
to write a.isGreatherThan(b)
. Zeliba gives you the ability to do it.
See examples TheComparable, TheChronoLocalDate, TheChonoDateTime
Usually, util methods start with is
prefix (like isEmpty
), but negations are covered via exclamation mark !is
,
which also makes you do calculations. I.e. “if a collection is empty” transforms into
collection.isEmpty()
, but “if a collection is not empty” transforms into !collection.isEmpty()
which is read as “not the collection is empty”. It is obviously grammatically incorrect.
Util methods like if(isNotEmpty(collection))
do a great job but still remain grammatically incorrect.
We don’t say “if is not an empty collection”.
Zeliba provides the same methods but also gives you a fluent API to write grammatically correct
code.
See examples. TheObject, TheCollection, TheMap
Inspired by when from Kotlin
.
Since Java 12 case
-expressions were extended. But Java 8
is still
widely used and it's nice to have some fluent API which is more useful than case
for pattern matching.
Zeliba provides some pattern-matching features.
See When
The examples reflect the master branch.
Let's assume we have two comparable objects.
BigDecimal val1 = ...
BigDecimal val2 = ...
Usually we check val1 > val2
like if (val1.compareTo(val2) > 0)
But with TheComparable
it's much easier to read
if (the(val1).isGreaterThan(val2)) {
...
}
if (the(val2).isLessThan(val1)) {
...
}
Fluent interval checks
val1 <= value <= val2
if (the(value).isInTheInterval().fromIncluded(val1).toIncluded(val2)) {
//...
}
val1 < value < val2
if (the(value).isInTheInterval().fromExcluded(val1).toExcluded(val2)) {
//...
}
val1 < value <= val2
if (the(value).isInTheInterval().fromExcluded(val1).toIncluded(val2)) {
//...
}
Also, there are extensions to compare dates
LocalDate someDate = ...
LocalDate otherDate = ...
if (the(otherDate).isAfterOrEqual(someDate)) {
...
}
if (the(someDate).isNotAfter(otherDate)) {
...
}
if (the(otherDate).isBeforeOrEqual(someDate)) {
...
}
if (the(someDate).isNotBefore(otherDate)) {
...
}
The same for DateTime
LocalDateTime someDateTime = ...
LocalDateTime otherDateTime = ...
if (the(otherDate).isAfterOrEqual(someDateTime)) {
...
}
if (the(someDate).isNotAfter(otherDateTime)) {
...
}
if (the(otherDate).isBeforeOrEqual(someDateTime)) {
...
}
if (the(someDate).isNotBefore(otherDateTime)) {
...
}
Fluent null and not equals checks
Object someObject = ...
Object otherObject = ...
if (the(otherObject).isNotEqualTo(someObject)) {
...
}
if (the(someObject).isNotNull()) {
...
}
if (the(someObject).isNull()) {
...
}
Fluent checks for empty/blank + avoiding NPE
String str1 = null;
String str2 = "abcd";
if (the(str1).isEmpty()) { ... } // returns false
if (the(str2).isNotEmpty()) { ... } // returns true
String str1 = null;
String str2 = "abcd";
if (the(str1).isBlank()) { ... } // returns true
if (the(str2).isNotBlank()) { ... } // returns false
Max possible substring
String str = "abcd"
String s = the(str).substring(2, 50); // returns "cd"
String s = the(str).substring(-2, 2); // returns "ab"
Replaces a char at given index
String str = "abcd"
String s = the(str).replaceAt(0, 'x'); // returns "xbcd"
String s = the(str).replaceAt(6, 'x'); // returns "abcd"
Grammatically correct fluent checks if a collection is null or is not empty
List<?> list = ...
if (the(list).isNotEmpty()) { ... }
Set<?> otherSet = null;
if (the(otherSet).isEmpty()) { ... } // returns true
Map<?,?> map = ...
Pair<?,?> pair = ... // Apache Commons Pair<> or any Map.Entry<>
if (the(map).isNotEmpty()) {
...
}
Fluent contains
checks to check if a map contains the particular entry.
Or if the particular key has the particular value.
if (the(map).contains(pair)) {
...
}
if (the(map).contains(key, value)) {
...
}
if (the(map).contains(entry(key, value))) {
...
}
Map.get(key)
returns null
if there is no value. TheMap allows to a map return an Optional<>
.
Optional<?> value = the(map).get(key)
Pattern-ish matching in pure Java 8
int value = ...
String result = when(value)
.is(1).then("+")
.is(0).then("zero")
.is(-1).then("-")
.orElse("?");
The when
returns value from the first matched predicate.
int value = 42
String result = when(value)
.is(42).then("first_42") //result=first_42
.is(42).then("second_42")
.orElse("?");
The is
part accepts Predicate
or a value which be compared as Objects.equals
String result = when(value)
.is(v -> v > 0).then("+")
.is(0).then("zero") // Objects.equals(0, value)
.is(v -> v < 0).then("-")
.orElse("?");
There is an opposise predicate isNot
String result = when(value)
.isNot(42).then("not 42")
.orElse("42 for sure");
To make a conjunction of few is
-predicates, and
can be used.
int value = 5;
String result = when(value)
.is(v -> v > 0).and(v-> v < 3).then("(0..3)")
.is(v -> v > 3).and(v-> v < 7).then("(3..7)")
.orElse("?");
To make a disjunction of few is
-predicates, or
can be used.
int value = 5;
String result = when(value)
.is(0).or(2).or(4).then("0 or 2 or 4")
.is(1).or(3).or(5).then("1 or 3 or 5")
.orElse("?");
or
and and
can be used together
int value = 5;
String result = when(value)
.is(1).or(2).then("< 3")
.is(v -> v > 6).and(v -> v < 10).or(5).then("(6;10) or 5")
.is(v -> v > 0).and(v -> v < 5)
.or(v -> v > 5).and(v -> v < 10).then("(0;5) or (5;10)")
.orElse("?");
then
part accepts a value, Supplier
or Function
.
The function accepts the initial value.
int value = ...
String result = when(value)
.is(1).then("+")
.is(0).then(() -> "zero")
.is(v -> v < 0).then(val -> String.valueOf(Math.abs(val))) // string of abs(value)
.orElse("?");
It is also possible to throw an exception from then
part
int value = ...
String result = when(value)
.is(1).then("+")
.is(0).then(() -> {
throw new RuntimeException();
})
.orElse("?");
orElse
accepts the same parameters as then
String result = when(value)
.is(1).then("1")
.orElse("not 1");
String result = when(value)
.is(1).then("1")
.orElse(this::method); // method will be called only if value is not 1
String result = when(value)
.is(1).then("1")
.orElse(val -> String.valueOf(Math.abs(val)));
By default orElseThrow
throws IllegalStateException
with default message.
orElseThrow
accepts a String
to set an exception message, or Supplier
to throw a custom one.
String result = when(value)
.is(1).then("1")
.orElseThrow(); // IllegalStateException with default message
String result = when(value)
.is(1).then("1")
.orElseThrow("Some valuable message");
String result = when(value)
.is(1).then("1")
.orElseThrow(RuntimeException::new);
If the absence of the result is normal flow. Optional<>
can be used as a return value.
int value = 1;
Optional<String> result = when(value)
.is(0).then("0")
.is(1).then("1")
.asOptional(); // Optional.of("1")
Optional<String> result = when(value)
.is(0).then("0")
.is(2).then("2")
.asOptional(); // Optional.empty()
String result = when(value)
.is(i -> i < 0).then(i -> String.format("negative %s", -i))
.is(0).then("zero")
.is(1).then(() -> String.format("positive %s", value))
.is(100_500).then(() -> {
throw new RuntimeException();
})
.isNot(42).then("not 42")
.orElseThrow("Custom exception message");
It's possible to make matching with two variables
int x = 1;
int y = -2;
String result = when(x, y)
.is(0, 0).then("zero")
.is(p -> p > 0, p -> p > 0).then("I Quadrant")
.is(p -> p < 0, p -> p > 0).then("II Quadrant")
.is(p -> p < 0, p -> p < 0).then("III Quadrant")
.is(p -> p > 0, p -> p < 0).then("IV Quadrant")
.orElse("??");
and
is also supported
int x = 1, y = 1;
String result = when(x, y)
.is((v1, v2) -> v1 + v2 < 0).and((v1, v2) -> v1 + v2 > -10).then("x+y=(-10..0)")
.is((v1, v2) -> v1 + v2 > -0).and((v1, v2) -> v1 + v2 < 10).then("x+y=(0..10)")
.is((v1, v2) -> v1 + v2 > 10).and((v1, v2) -> v1 + v2 < 20).then("x+y=(10..20)")
.orElseThrow();
or
and and
can be used together
String result = when(x, y)
.is(2, 2).or(3, 3).or(4, 4).then("2-3-4")
.isNot(1, 1).and(p -> p > 0, p -> p > 0).then("not 1, > 0")
.is(1, 2).or(2, 1).or(1, 1).then("1 or 2")
.orElseThrow();
This project is licensed under Apache License, version 2.0
Releases are available in Maven Central
Add this snippet to the pom.xml dependencies
section:
<dependency>
<groupId>me.dehasi</groupId>
<artifactId>zeliba</artifactId>
<version>2021.06.22</version>
</dependency>
Add this snippet to the build.gradle dependencies
section:
implementation 'me.dehasi:zeliba:2021.06.22'
Feel free to share your ideas via issues and pull-requests.