Skip to content

Commit

Permalink
#19: Added documentation for primitive value helpers
Browse files Browse the repository at this point in the history
* Completed GitBook documentation
* Moved Primitives out of incubation
  • Loading branch information
Boereck committed Apr 9, 2019
1 parent 914e847 commit c9a17ee
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 16 deletions.
1 change: 1 addition & 0 deletions bundles/de.fhg.fokus.xtensions/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Export-Package: de.fhg.fokus.xtensions.concurrent,
de.fhg.fokus.xtensions.iteration,
de.fhg.fokus.xtensions.optional,
de.fhg.fokus.xtensions.pair,
de.fhg.fokus.xtensions.primitives,
de.fhg.fokus.xtensions.range,
de.fhg.fokus.xtensions.stream,
de.fhg.fokus.xtensions.string
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package de.fhg.fokus.xtensions.incubation
package de.fhg.fokus.xtensions.primitives

import java.util.function.IntSupplier
import java.util.function.LongSupplier
import java.util.function.DoubleSupplier
import static de.fhg.fokus.xtensions.incubation.Primitives.BoolUnaryOperator.*
import static de.fhg.fokus.xtensions.primitives.Primitives.BoolUnaryOperator.*
import java.util.Objects
import java.util.function.BooleanSupplier
import java.util.function.ToIntFunction
Expand All @@ -20,7 +20,7 @@ import java.util.function.ToDoubleFunction
* second to last step will cause the chain to implicitly return of a primitive value specific default value.
* E.g. in the chain {@code foo?.bar?.baz?.someBool} will implicitly return {@code false}
* if at {@code baz} the chain-expression is evaluated to {@code null}. This can be
* considered as a code smell.<br><br>
* considered as a code smell.
*/
final class Primitives {

Expand All @@ -37,6 +37,12 @@ final class Primitives {
* at the end of the call chain. An advantage of this type of comparison is that the {@code box} method can
* also be called with a null-safe navigation to have a uniform navigation pattern for the call
* chain.
* @param context object that will be passed to {@code mapper} if not {@code null}
* @param mapper function that {@code context} will be passed to and is supposed to return a boolean
* property of {@code context}
* @return will return {@code null} if {@code context} is {@code null}, otherwise will return the
* value returned from {@code mapper} applied to the {@code context} object
* @throws NullPointerException if {@code mapper} is {@code null}
*/
static def <T> Boolean box(T context, (T)=>boolean mapper) {
Objects.requireNonNull(mapper, "mapper function is not allowed to be null")
Expand All @@ -57,6 +63,12 @@ final class Primitives {
* at the end of the call chain. An advantage of this type of comparison is that the {@code box} method can
* also be called with a null-safe navigation to have a uniform navigation pattern for the call
* chain.
* @param context object that will be passed to {@code mapper} if not {@code null}
* @param mapper function that {@code context} will be passed to and is supposed to return a primitive
* number value property of {@code context}
* @return will return {@code null} if {@code context} is {@code null}, otherwise will return the
* value returned from {@code mapper} applied to the {@code context} object
* @throws NullPointerException if {@code mapper} is {@code null}
*/
static def <T, N extends Number> N boxNum(T context, (T)=>N mapper) {
Objects.requireNonNull(mapper, "mapper function is not allowed to be null")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* This package holds the class {@ link de.fhg.fokus.xtensions.primitives.Primitives}
* providing extension methods to handle primitive values at the end of null-safe navigation chains.
*/
package de.fhg.fokus.xtensions.primitives;
60 changes: 49 additions & 11 deletions docs/functionality/16_primitives.adoc
Original file line number Diff line number Diff line change
@@ -1,64 +1,93 @@

== Primitives

The class `de.fhg.fokus.xtensions.Primitives` provides a bunch of static extension methods
The class `de.fhg.fokus.xtensions.primitives.Primitives` provides a bunch of static extension methods
that are aimed to help handling primitive values at the end of null-safe navigation chains.


=== Boxing Primitives

To box the primitive value of a property at the end of a null-safe call chain, the `Primitives`
class provides the `box` and `boxNum` extension functions. These are intended to be used on
the object, that's primitive property should be boxed. The function passed to the box function
a context object, that's primitive property should be boxed. The function passed to the box function
is used to retrieve the primitive property.

Example:

[source,xtend]
----
import static extension de.fhg.fokus.xtensions.Primitives.*
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
person?.lastName.boxNum[length]
person?.address.boxNum[floor]
----
In this example the last expression evaluates to an `Integer` which is `null` if `person` or `lastName`
is `null`. Otherwise it will hold the length of the of the `lastName` string.
In this example the last expression evaluates to an `Integer` which is `null` if `person` or `address`
is `null`. Otherwise it will hold a boxed `Integer` wrapping the `int` value of the `floor` property of `address`.
These boxing functions can be used both directly or using null-safe navigation. Using `?.`, however will perform
unnecessary null-checks.

=== Testing Conditions
It makes sense to call `onNull` functions subsequent to the boxing functions as described in <<Default Values on `null` Boxes>>.


=== Testing Conditions on Primitive Values

Boxed boolean values (e.g. produced by functions described in <<Boxing Primitives>>) can be tested
directly with the `null`-aware extension methods `isTrue`, `isFalse`, `isNullOrTrue`, `isNullOrFalse`.

These functions have to be called directly, not with null-safe navigation.

Example:

[source,xtend]
----
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
person?.address.box[isValidated].isTrue
----
In this example if `person` and `address` are not `null` the `box` function will
return the boxed boolean value of attribute `isValidated`, otherwise `null`. The
call to `isTrue` will then check if the the boxed integer is not `null` and wraps the
value `true`.

To test un-boxed primitives, one of the `isTrue`, `isFalse`, `isNullOrTrue`, `isNullOrFalse` methods.

To test if un-boxed primitives at the end of null-safe navigation chain adhere to a certain condition,
one of the extension methods `isTrue`, `isFalse`, `isNullOrTrue`, or `isNullOrFalse` taking
a testing function can be used.

Example:

[source,xtend]
----
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
person?.address.isTrue[floor > 3]
----
The example expression will return `true` if `person` and address are not `null`
and the `floor` property of `address` greater than `3`, otherwise it will return `false`.


=== Conversion to Optionals

Similar to the <<Boxing Primitives>> functions, the `optionalInt`, `optionalLong` and `optionalDouble`
functions are supposed to box a primitive value property of a context object into a primitve optional value.
These extension functions however never return `null` . They return an empty optional if the the given context object is `null`. If the context object is not `null` the value returned by the given mapper function is wrapped into the returned optional.
These extension functions however never return `null` . They return an empty optional if the the given context object is `null`.
If the context object is not `null` the value returned by the given mapper function is wrapped into the returned optional.

Example:

[source,xtend]
----
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
val OptionalInt nameLen = person?.lastName.optionalInt[length]
----
If `person` and `lastName` in this example are not `null` the optional `nameLen`
will wrap the length of the `lastName` string. Otherwise `nameLen` will reference an
empty optional.

=== Default Values on `null` Boxes
=== Default Values for `null` Boxes

The `onNull` extension functions for boxed primitives check if a given reference to a boxed primitive value
and will compute a default value via a given supplier if the box reference is `null`, otherwise they return
Expand All @@ -68,7 +97,16 @@ Example:

[source,xtend]
----
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
person?.address.boxNum[floor].onNull[0]
----
In this example the `onNull` call will return `0` if the given boxed `Integer` is `null`,
otherwise it will unbox the wrapped `int` value and return it.
The behavior of the example expression is equivalent to the behavior of the expression `person?.address?.floor`
which will result in a compiler warning, due to an implicit return of `0` if the navigation chain before `floor`
evaluates to `null`.

[TIP]
====
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.fhg.fokus.xtensions.incubation
package de.fhg.fokus.xtensions.primitives

import static extension de.fhg.fokus.xtensions.incubation.Primitives.*
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
import static org.junit.Assert.*
import org.junit.Test
import java.util.OptionalInt
Expand Down

0 comments on commit c9a17ee

Please sign in to comment.