Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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: about 2 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 5 years ago)
- Default Branch: master
- Last Pushed: 2022-02-08T20:02:53.000Z (almost 3 years ago)
- Last Synced: 2024-01-23T09:37:53.047Z (11 months ago)
- Topics: fluent-api, fluent-checks, java-8, pattern-matching
- Language: Java
- Homepage:
- Size: 85.9 KB
- Stars: 7
- Watchers: 3
- 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.
[![Build Status](https://www.travis-ci.org/dehasi/zeliba.svg?branch=master)](https://www.travis-ci.org/dehasi/zeliba)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/me.dehasi/zeliba/badge.svg)](https://maven-badges.herokuapp.com/maven-central/me.dehasi/zeliba)
[![Test Coverage](https://img.shields.io/codecov/c/github/dehasi/zeliba/master)](https://codecov.io/github/dehasi/zeliba?branch=master)
![GitHub code size in bytes](https://img.shields.io/github/languages/code-size/dehasi/zeliba)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)) {
...
}
```
### TheChronoLocalDateTimeThe 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 = 42String 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("?");
```
#### orTo 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
```javaint 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.