https://github.com/dehasi/zeliba
A handy DSL-ish library to make comparisons more readable.
https://github.com/dehasi/zeliba
fluent-api fluent-checks java-8 pattern-matching
Last synced: 5 months ago
JSON representation
A handy DSL-ish library to make comparisons more readable.
- Host: GitHub
- URL: https://github.com/dehasi/zeliba
- Owner: dehasi
- License: apache-2.0
- Created: 2020-02-12T20:49:35.000Z (almost 6 years ago)
- Default Branch: master
- Last Pushed: 2025-05-22T22:09:36.000Z (9 months ago)
- Last Synced: 2025-05-22T23:27:54.779Z (9 months ago)
- Topics: fluent-api, fluent-checks, java-8, pattern-matching
- Language: Java
- Homepage:
- Size: 88.9 KB
- Stars: 8
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ZeLiba (The library)
A handy DSL-ish helper to make the comparison more readable.
[](https://www.travis-ci.org/dehasi/zeliba)
[](https://maven-badges.herokuapp.com/maven-central/me.dehasi/zeliba)
[](https://codecov.io/github/dehasi/zeliba?branch=master)

Zeliba provides a fluent API to write a comparison (for `Comparable`) and does other checks.
Inspired by [AssertJ](https://joel-costigliola.github.io/assertj/), [kotlin](https://kotlinlang.org/), [ZE FISH](https://vk.com/zefish)
- [Motivation](#Motivation)
- [Examples](#Examples)
- [TheComparable](#TheComparable)
- [TheChronoLocalDate](#TheChronoLocalDate)
- [TheChronoLocalDateTime](#TheChronoLocalDateTime)
- [TheObject](#TheObject)
- [TheString](#TheString)
- [isEmpty](#isEmpty)/[isBlank](#isBlank)
- [substring](#substring)
- [replaceAt](#replaceAt)
- [TheCollection](#TheCollection)
- [TheMap](#TheMap)
- [contains](#contains)
- [Optional get](#Optional-get)
- [When](#When)
- [is](#is)
- [isNot](#isNot)
- [and](#and)
- [or](#or)
- [then](#then)
- [orElse](#orElse)
- [orElseThrow](#orElseThrow)
- [asOptional](#asOptional)
- [Complex example](#Complex-example)
- [When2](#When2)
- [License](#License)
- [Installation](#Installation)
- [Maven](#Maven)
- [Gradle](#Gradle)
- [Contribution](#Contribution)
## Motivation
Zeliba main points are the following:
* Provide a fluent API to write a comparison (for `Comparable`)
* Make `if`-checks better align with English grammar
* Provide pattern matching for `Java 8`
### Fluent compatible
Java doesn't support operator overloading, you can’t write something like `a > b` for objects,
as an alternative you can use `Comparable`. 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](#TheComparable), [TheChronoLocalDate](#TheChronoLocalDate), [TheChonoDateTime](#TheChonoDateTime)
### Better English
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](#TheObject), [TheCollection](#TheCollection), [TheMap](#TheMap)
### Pattern matching
Inspired by [when](https://kotlinlang.org/docs/reference/control-flow.html#when-expression) from `Kotlin`.
Since [Java 12](https://openjdk.java.net/jeps/325) `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](#When)
## Examples
The examples reflect the master branch.
### TheComparable
Let's assume we have two comparable objects.
```java
BigDecimal val1 = ...
BigDecimal val2 = ...
```
Usually we check `val1 > val2` like `if (val1.compareTo(val2) > 0)`
But with `TheComparable` it's much easier to read
```java
if (the(val1).isGreaterThan(val2)) {
...
}
if (the(val2).isLessThan(val1)) {
...
}
```
Fluent interval checks
`val1 <= value <= val2`
```java
if (the(value).isInTheInterval().fromIncluded(val1).toIncluded(val2)) {
//...
}
```
`val1 < value < val2`
```java
if (the(value).isInTheInterval().fromExcluded(val1).toExcluded(val2)) {
//...
}
```
`val1 < value <= val2`
```java
if (the(value).isInTheInterval().fromExcluded(val1).toIncluded(val2)) {
//...
}
```
### TheChronoLocalDate
Also, there are extensions to compare dates
```java
LocalDate someDate = ...
LocalDate otherDate = ...
if (the(otherDate).isAfterOrEqual(someDate)) {
...
}
if (the(someDate).isNotAfter(otherDate)) {
...
}
if (the(otherDate).isBeforeOrEqual(someDate)) {
...
}
if (the(someDate).isNotBefore(otherDate)) {
...
}
```
### TheChronoLocalDateTime
The same for `DateTime`
```java
LocalDateTime someDateTime = ...
LocalDateTime otherDateTime = ...
if (the(otherDate).isAfterOrEqual(someDateTime)) {
...
}
if (the(someDate).isNotAfter(otherDateTime)) {
...
}
if (the(otherDate).isBeforeOrEqual(someDateTime)) {
...
}
if (the(someDate).isNotBefore(otherDateTime)) {
...
}
```
### TheObject
Fluent null and not equals checks
```java
Object someObject = ...
Object otherObject = ...
if (the(otherObject).isNotEqualTo(someObject)) {
...
}
if (the(someObject).isNotNull()) {
...
}
if (the(someObject).isNull()) {
...
}
```
### TheString
Fluent checks for empty/blank + avoiding `NPE`
#### isEmpty
```Java
String str1 = null;
String str2 = "abcd";
if (the(str1).isEmpty()) { ... } // returns false
if (the(str2).isNotEmpty()) { ... } // returns true
```
#### isBlank
```Java
String str1 = null;
String str2 = "abcd";
if (the(str1).isBlank()) { ... } // returns true
if (the(str2).isNotBlank()) { ... } // returns false
```
#### substring
Max possible substring
```java
String str = "abcd"
String s = the(str).substring(2, 50); // returns "cd"
String s = the(str).substring(-2, 2); // returns "ab"
```
#### replaceAt
Replaces a char at given index
```java
String str = "abcd"
String s = the(str).replaceAt(0, 'x'); // returns "xbcd"
String s = the(str).replaceAt(6, 'x'); // returns "abcd"
```
### TheCollection
Grammatically correct fluent checks if a collection is null or is not empty
```java
List> list = ...
if (the(list).isNotEmpty()) { ... }
Set> otherSet = null;
if (the(otherSet).isEmpty()) { ... } // returns true
```
### TheMap
```java
Map,?> map = ...
Pair,?> pair = ... // Apache Commons Pair<> or any Map.Entry<>
if (the(map).isNotEmpty()) {
...
}
```
#### contains
Fluent `contains` checks to check if a map contains the particular entry.
Or if the particular key has the particular value.
```java
if (the(map).contains(pair)) {
...
}
if (the(map).contains(key, value)) {
...
}
if (the(map).contains(entry(key, value))) {
...
}
```
#### Optional `get`
`Map.get(key)` returns `null` if there is no value. TheMap allows to a map return an `Optional<>`.
```java
Optional> value = the(map).get(key)
```
### When
Pattern-ish matching in pure `Java 8`
```java
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.
```java
int value = 42
String result = when(value)
.is(42).then("first_42") //result=first_42
.is(42).then("second_42")
.orElse("?");
```
#### is
The `is` part accepts `Predicate` or a value which be compared as `Objects.equals`
```java
String result = when(value)
.is(v -> v > 0).then("+")
.is(0).then("zero") // Objects.equals(0, value)
.is(v -> v < 0).then("-")
.orElse("?");
```
#### isNot
There is an opposise predicate `isNot`
```java
String result = when(value)
.isNot(42).then("not 42")
.orElse("42 for sure");
```
#### and
To make a conjunction of few `is`-predicates, `and` can be used.
```java
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("?");
```
#### or
To make a disjunction of few `is`-predicates, `or` can be used.
```java
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
```java
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
`then` part accepts a value, `Supplier` or `Function`.
The function accepts the initial value.
```java
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
```java
int value = ...
String result = when(value)
.is(1).then("+")
.is(0).then(() -> {
throw new RuntimeException();
})
.orElse("?");
```
#### orElse
`orElse` accepts the same parameters as [then](#then)
```java
String result = when(value)
.is(1).then("1")
.orElse("not 1");
```
```java
String result = when(value)
.is(1).then("1")
.orElse(this::method); // method will be called only if value is not 1
```
```java
String result = when(value)
.is(1).then("1")
.orElse(val -> String.valueOf(Math.abs(val)));
```
#### orElseThrow
By default `orElseThrow` throws `IllegalStateException` with default message.
`orElseThrow` accepts a `String` to set an exception message, or `Supplier` to throw a custom one.
```java
String result = when(value)
.is(1).then("1")
.orElseThrow(); // IllegalStateException with default message
```
```java
String result = when(value)
.is(1).then("1")
.orElseThrow("Some valuable message");
```
```java
String result = when(value)
.is(1).then("1")
.orElseThrow(RuntimeException::new);
```
#### asOptional
If the absence of the result is normal flow. `Optional<>` can be used as a return value.
```java
int value = 1;
Optional result = when(value)
.is(0).then("0")
.is(1).then("1")
.asOptional(); // Optional.of("1")
Optional result = when(value)
.is(0).then("0")
.is(2).then("2")
.asOptional(); // Optional.empty()
```
#### Complex example
```java
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");
```
### When2
It's possible to make matching with two variables
```java
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
```java
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
```java
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();
```
## License
This project is licensed under [Apache License, version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
## Installation
Releases are available in [Maven Central](https://repo1.maven.org/maven2/me/dehasi/zeliba/)
### Maven
Add this snippet to the pom.xml `dependencies` section:
```xml
me.dehasi
zeliba
2021.06.22
```
### Gradle
Add this snippet to the build.gradle `dependencies` section:
```groovy
implementation 'me.dehasi:zeliba:2021.06.22'
```
## Contribution
Feel free to share your ideas via issues and pull-requests.