import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val Optional<String> no = none
val Optional<String> yes = some("yesss!")
val Optional<String> dunno = maybe(possiblyNull())
// ...
private def String possiblyNull() {
if(System.currentTimeMillis % 2 == 0) {
"I'm in ur optional"
} else {
null
}
}
Provided Functionality
If you want to know how to add the library to your project, have a look at the Introduction.
The sub-chapters of this chapter will provide a high level overview on how to use the different parts of this library.
When using the library in OSGi it is recommended to use package imports since the library may evolve and split into multiple bundles in future releases.
The following chapters will give an overview over the most important extensions and types provided by the library. It does not include all methods. Please have a look at the sources or the JavaDocs to explore all available functionality.
Most of the given examples in the following sections and some more are defined in the following test class: Showcase.xtend
If you want to see the overview all chapters of the documentation, have a look at the Content page.
- Extensions to Optional
- Extensions to Primitive Optionals
- OptionalBoolean
- Extensions to IntegerRange
- Extensions to Pair
- Extensions to Primitive Arrays
- Extensions to Streams
- Extensions to Streams of Strings
- Extensions to Iterable
- Extensions to Iterator
- Primitive Iterables
- Extensions to PrimitiveIterators
- Extensions to String
- Extensions to Duration
- Extensions to Functions
- Extensions to CompletableFuture
- Async Computations
- Scheduling Util
- Primitives
- Objects
Extensions to Optional
The static factory Methods for creating Optional
instances are not really meant to be used as
statically imported methods. They have no meaningful names to be used this way. They also differ from
the commonly used names some
, none
and maybe
which are used in many other languages.
The class OptionalIntExtensions
provides static factory methods with these common names
which are implemented to be inlined to the factory methods used by the JDK.
Examples:
The optional class does not provide a filter
method, that filters the optional based on the class
the wrapped object is instance of, as known e.g. from Xtend’s filter methods on Iterable
.
The OptionalIntExtensions
adds such a method, providing an instance check of the wrapped value.
Example:
import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val Optional<Object> optObj = some("Hi there!")
val Optional<String> optStr = optObj.filter(String)
optStr.ifPresent [
println(it.toUpperCase)
]
When testing an Optional
for a value or otherwise perform a different operation
the optional has to be used twice e.g.
import java.util.Optional
// ...
val noVal = Optional.empty
if(noVal.isPresent) {
val value = noVal.get
println("Here is your value: "+ value)
} else {
println("Awww, no value")
}
This can be error prone, since the optional (in the example noVal
) has to be
used twice and a different optional may be used accidently. To not run into this
issue this library provides the whenPresent
method which allows defining an
else branch on the returned object.
Example:
import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val noVal = Optional.empty
noVal.whenPresent [
println("Here is your value: "+ it)
].elseDo [
println("Awww, no value")
]
Alternatively, the ifPresentOrElse
extension method can be used, but this does not
have a clear visual separation which case is the if and which the else callback.
To avoid allocating objects over and over for the lambda passed to the
elseDo
method, there are overloaded versions of the method passing on
additional parameters to the lambda. This can avoid "capturing" lambdas
which would create a new object on every elseDo
call.
Example:
import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val captureMe = "no value"
val noVal = Optional.empty
noVal.whenPresent [
println("Here is your value: "+ it)
].elseDo(captureMe) [
println("Awww, " + it)
]
To bridge between APIs providing an Optional
value and ones that expect
multiple values, the extension methods asIterable
, toList
and toSet
are provided to create immutable implementations of common JVM collection APIs.
The Optional
class has a map
method that can map the value present in the optional
to a value of another type. Unfortunately there is no method to map to a primitive type
returning a primitive optional, such as OptionalInt
. The extension methods mapInt
,
mapLong
, and mapDouble
allow mapping to primitive options without having to
box the resulting value. To map to a boolean
value the method test
can be used,
which returns an OptionalBoolean. This extension method is mostly intended to check
a property on a value wrapped in an Optional
.
Example:
import java.util.Optional
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val Optional<String> yes = some("yesss!")
val OptionalInt lenOpt = yes.mapInt[length]
val len = lenOpt.orElse(0)
println("Length is " + len)
The call chain map[…].orElseGet[..]
is pretty common. As a shortcut the method mapOrGet
is provided.
Some methods on Optional introduced in Java 9 are available as retrofitted extension methods. When compiling a class using the extension method targeting Java 9, the native Optional method has precedence and will be used. No changes in the source code has to be done to switch to the native Java 9 implementation. The following instance methods of Optional are backported for Java 8:
As a shortcut for the or
extension method, the ||
operator is provided. The ?:
operator is a shortcut for the orElse
method on Optional.
Tip
|
Related JavaDocs: |
Extensions to Primitive Optionals
Extensions to the primitive versions of Optional are provided by the following classes:
de.fhg.fokus.xtensions.optional.OptionalIntExtensions de.fhg.fokus.xtensions.optional.OptionalLongExtensions de.fhg.fokus.xtensions.optional.OptionalDoubleExtensions
Same as for Optional, there is a some
alias for the OptionalInt.of
, OptionalLong.of
, and OptionalDouble.of
methods (see Extensions to Optional).
The methods noInt
, noLong
, and noDouble
provide empty primitive Optionals.
The Open JDK / Oracle JDK currently does not cache OptionalInt and OptionalLong instances in the static factory method
OptionalInt.of(int)
and OptionalLong.of(long)
as it is currently done for Integer creation in
Integer.valueOf(int)
. To provide such a caching static factory methods, the
OptionalIntExtensions.someOf(int)
and OptionalLongExtensions.someOf(long)
method were
introduced.
Example:
import static de.fhg.fokus.xtensions.optional.OptionalIntExtensions.*
// ...
if(someOf(42) === someOf(42)) {
println("someOf caches instances")
}
Stunningly, the primitive versions of Optional do not provide map
and filter
methods. These
are provided as extension methods by this library.
OptionalBoolean
The class de.fhg.fokus.xtensions.optional.OptionalBoolean
represents a
boolean
value that may be absent. As with other optional types,
references to objects of this type should never be null
! It is mostly intended to be
used as a return value from methods to allow for fluent call chains.
Creation
The static factory method empty
creates an OptionalBoolean
not holding a value.
Creating an OptionalBoolean
from a Boolean
that may be null
, the ofNullable
factory method can be used; an alias meant to be used as an extension method is the asOptional
factory method. To create an OptionalBoolean
from a boolean
value, definitely holding
a value, the static of
factory method can be used. The static methods ofTrue
and ofFalse
provide OptionalBoolean
wrapping true
or false
.
Testing for Content
To check if an OptionalBoolean
is either empty, wrap true
or wrap false
, the class
provides several methods:
-
boolean isEmpty()
-
boolean isPresent()
-
boolean isTrue()
-
boolean isTrueOrEmpty()
-
boolean isFalse()
-
boolean isFalseOrEmpty()
Note that isTrue
and isFalse
return false
if the OptionalBoolean
is empty.
Analogous to the methods mentioned above there are methods that test for a value and execute a
given callback if the condition applies. These methods have the if
prefix, e.g. void ifTrue(()⇒void then)
.
Additional the void ifPresentOrElse(BooleanConsumer action, Runnable emptyAction)
method
takes two callbacks, one for the case of an optional with value, and one that is invoked if the optional is empty.
import de.fhg.fokus.xtensions.optional.OptionalBoolean
// ...
val random = new Random()
val randomBool = if (random.nextBoolean) {
OptionalBoolean.empty
} else {
OptionalBoolean.of(random.nextBoolean)
}
// will not print on empty optional
randomBool.ifTrue [
println("Yippie! A random true!")
]
Extracting Values
There are several methods that allow to extract a wrapped value, and handle the case of an empty optional in different ways.
-
orElse(boolean fallback)
returns a given default value on empty, -
orElseGet(BooleanSupplier fallback)
computes a default value (viafallback
) on empty -
<X extends Throwable> boolean orElseThrow(Supplier<? extends X> exceptionSupplier) throws X
throws an exception provided byexceptionSupplier
on empty
Note that the OptionalBoolean
does not have a simple get
method which throws an
NoSuchElementException
as other optionals have. However the method getNullable
returns a Boolean
which is null
if the optional is empty.
Example:
import de.fhg.fokus.xtensions.optional.OptionalBoolean
import static extension de.fhg.fokus.xtensions.optional.OptionalExtensions.*
// ...
val names = #["Mike", "Joe", "Christian"]
val longNameWithC = names.stream
.filter[length > 4]
.findFirst
.test[startsWith("C")]
.orElse(false)
if(longNameWithC) {
println("The long name starts with 'C'")
}
Note that the test
method is an extension method on Optional
provided by OptionalExtensions
returning an OptionalBoolean
.
Tip
|
Related JavaDocs: |
Extensions to IntegerRange
IntegerRange is a handy type from the Xtend standard library which can
be constructed using the ..
operator. But the only way to iterate
over the elements of the range is by boxing the integers while iterating.
The extensions provided by this library allow iterating over the primitive values of the range.
One way to iterate over the range is to use Java 8 streams, by using the
stream
or parallelStream
extension method from the class
de.fhg.fokus.xtensions.range.RangeExtensions
.
Exmaple:
import static extension de.fhg.fokus.xtensions.range.RangeExtensions.*
// ...
val range = (0..20).withStep(2)
range.stream.filter[it % 5 == 0].sum
Another way to iterate over the elements of a range is to use the forEachInt
method.
Example:
import static extension de.fhg.fokus.xtensions.range.RangeExtensions.*
// ...
val range = (0..20).withStep(2)
range.forEachInt [
println(it)
]
To interact with consumers expecting an IntIterable
(see Primitive Iterables), which is a generic interface
for iteration over primitive int values provided by this library, the extension method
asIntIterable
was provided.
Tip
|
Related JavaDocs: |
Extensions to Pair
The class de.fhg.fokus.xtensions.pair.PairExtensions
provides extension methods for the type
org.eclipse.xtext.xbase.lib.Pair
.
The with-operator ⇒
can be used to destructure a Pair into key
and value
and returns the input Pair.
Example:
import static extension de.fhg.fokus.xtensions.pair.PairExtensions.*
// ...
val pair = "Foo" -> 3
pair => [k,v|
println(k + ' -> ' + v)
]
The combine
extension method takes a function to which key and value of a Pair is passed to,
to merge both objects. The result returned by the function will be returned by the combine
method.
The difference to the >>>
operator, provided by the Extensions to Functions
is only that due to operator precedence calling further methods on the result needs further braces.
Example:
import static extension de.fhg.fokus.xtensions.pair.PairExtensions.*
// ...
val pair = "Foo" -> 3
val s = pair.combine[k,v| k + ' = ' + v].toLowerCase
println(s)
Tip
|
Related JavaDocs: |
Extensions to Primitive Arrays
The class de.fhg.fokus.xtensions.iteration.PrimitiveArrayExtensions
contains extension methods for
arrays of primitive values (int, long, double) to iterate with a forEach method consuming primitive values.
Example:
import static extension de.fhg.fokus.xtensions.iteration.PrimitiveArrayExtensions.*
// ...
val int[] arr = #[3,4,6]
arr.forEachInt [
println(it)
]
Additionally the class allows to create primitive iterable wrapper objects (see Primitive Iterables).
Tip
|
The JDK class java.util.Arrays already contains
static stream methods that can be used as extension methods to create Java 8 streams from primitive arrays.
|
Tip
|
Related JavaDocs: |
Extensions to Streams
The class de.fhg.fokus.xtensions.stream.StreamExtensions
provides extension
methods to the java.util.stream.Stream
interface.
Java 8 streams are missing a few methods known from the Xtend iterable extension methods.
The one method that is probably most often used is the method to filter by type. This can easily
be retrofitted on the Streams API by an extension method. This extension method is provided
in the StreamExtensions
class.
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StreamExtensions.*
// ...
val s = Stream.of(42, "Hello", Double.NaN, "World")
.filter(String)
.collect(Collectors.joining(" "))
Tip
|
Since joining Strings is a common operation, the StringStreamExtensions allow to call join
directly on the Stream. Have a look at Extensions to Streams of Strings.
|
Some other collectors, especially the ones bridging to the collections API are also used very often,
but using the collect method with the methods from the Collectors
class is a bit verbose.
As a shortcut the StreamExtensions
class provides toList
, toSet
, and toCollection
extension methods to the Stream
class.
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StreamExtensions.*
// ...
val list = Stream.of("Foo", "Hello" , "Boo", "World")
.filter[!contains("oo")]
.map[toUpperCase]
.toList
A useful extension method from Xtend on java.lang.Iterable
is the filterNull
method, which
produces a view for an iterable excluding the null
elements. An equivalent is not provided on the
Stream
interface. This library provides such an extension method on stream.
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StreamExtensions.*
// ...
Stream.of(42.0, null, "foo", 100_000_000_000bi)
.filterNull
.forEach [
// it is guaranteed to be != null
println(it.toString.toUpperCase)
]
As a shortcut for the
concat
method the StreamExtensions
class provides a +
operator.
The flatMap
method on Stream
expects a function mapping to another stream. Oftentimes data structures
do not provide streams, but Collection
or Iterable
types, so the user has to create a stream based on
them. This usually leads to some visual noise. This library provides a flatMap
extension method which allows to
be called with a function providing an iterable, since it is known how to construct a stream from an iterable.
Example:
import org.eclipse.xtend.lib.annotations.Accessors
import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor
import java.util.stream.Stream
import java.util.function.Function
import static java.util.stream.Collectors.*
import static extension de.fhg.fokus.xtensions.stream.StreamExtensions.*
// ...
val stream = Stream.of(
new Developer("Max", #{"Java", "Xtend", "Rust", "C++"}),
new Developer("Joe", #{"Xtend", "JavaScript", "Dart"})
);
// Mapping language name to number of occurrences
val Map<String, Long> langPopularity = stream
.flatMap[languages] (1)
.collect(groupingBy(Function.identity, counting))
langPopularity.entrySet
.stream
.max(Map.Entry.comparingByValue)
.ifPresent [
println('''Most pobular language: «it.key», count: «it.value»''')
]
// ...
@FinalFieldsConstructor
@Accessors
static class Developer {
val String name
val Set<String> languages;
}
-
Here
languages
can be returned directly instead oflanguages.stream
Sometimes it is interesting to produce the cartesian product of two containers of elements. To produce
all combinations of the elements of a stream with the elements of an Iterable
(or a different source
of a stream) this library provides the combinations
extension methods. If no merging function is
provided, the combinations
extension methods will create a org.eclipse.xtext.xbase.lib.Pair
object for each combination. If a merging function is provided, the resulting stream will hold the result
of the merge of each combination.
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StreamExtensions.*
// ...
Stream.of("foo", "bar")
.combinations(#["fun", "boo", "faz"])[a,b|a+b]
.forEach[
println(it)
]
Java 9 provides a static factory methods for an infinite stream
Stream.iterate(T,UnaryOperator<T>). A function with the same functionality is provided via StreamExtensions
.
There is even an overloaded version of the static method that can be written as if the method would exist in the Stream class:
// This is using Java 8
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StreamExtensions.*
// ...
Stream.iterate("na ")[it + it]
.filter[length > 15]
.findFirst
.ifPresent [
println(it + "Batman!")
]
This method can be handy traversing a nested data structure of same-type elements (e.g. moving up a containment hierarchy).
Tip
|
Related JavaDocs: |
Extensions to Streams of Strings
Since Xtend can provide extension methods specifically for specializations of generic types,
it is possible to provide methods only available for java.util.stream.Stream<String>
.
The class de.fhg.fokus.xtensions.stream.StringStreamExtensions
provides such extension methods.
The most used collectors on streams of strings are the joining collectors from java.util.stream.Collectors
.
To make these easy to use join
methods have been introduced as extension methods to Stream<String>
.
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StringStreamExtensions.*
// ...
val joined = Stream.of("Hello", "Xtend", "aficionados").join(" ")
println(joined)
Another operation often performed on streams of strings is filtering it based on a regular expression.
This is provided via the extension method matching
. The pattern can either be passed in as string
or as a pre-compiled java.util.regex.Pattern
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StringStreamExtensions.*
// ...
Stream.of("foo", "bar", "kazoo", "baz", "oomph", "shoot")
.matching(".+oo.*")
.forEach [
println(it)
]
When splitting strings provided as a stream it is handy to get an operation providing a single stream of the result of splitting all elements, which also works as lazy as possible. A use case would be to to use Files.lines(Path) and then split the resulting lines of this operation.
Example:
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StringStreamExtensions.*
// ...
Stream.of("Hello users", "welcome to this demo", "hope it helps")
.flatSplit("\\s+")
.forEach [
println(it)
]
Sometimes it is also wanted to find all matches of a regular expressions in a stream of strings and
produce a single stream of all the matches in all strings. This can be done using the flatMatches
extension method. The pattern of the regular expression can either be provided as a string or as a
pre-compiled java.util.regex.Pattern
object.
Example:
import java.util.regex.Pattern
import java.util.stream.Stream
import static extension de.fhg.fokus.xtensions.stream.StringStreamExtensions.*
// ...
val Pattern pattern = Pattern.compile("(\\woo)")
Stream.of("Welcome to the zoo", "Where cows do moo", "And all animals poo")
.flatMatches(pattern)
.forEach [
println(it)
]
Tip
|
Related JavaDocs: |
Extensions to Iterable
The de.fhg.fokus.xtensions.iteration.IterableExtensions
class provides extension methods to
java.lang.Iterable
Unfortunately the java.lang.Iterable
interface does not provide a (default)
method for creating a java.lang.Stream
. It does provide a method to obtain a
Spliterator
which can be used to create a stream, but this is rather unpleasant to use.
The IterableExtensions
class provides the stream
extension method to easily create
a stream from an iterable. This method will first check if the given iterable is instance of
java.util.Collection
, since this class does provide a default stream
method,
otherwise it will construct a stream from the spliterator provided by the iterable.
Example:
import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
import java.util.OptionalDouble
//...
#["foo", null, "BAR", "bazzzz"]
.filterNull
.averageSize
.ifPresent [
println('''The average string lenght is «it»''')
]
//...
private def OptionalDouble averageSize(Iterable<String> strings) {
strings.stream.mapToInt[length].average (1)
}
-
In this line the extension method
stream
is called on the iterablestrings
.
Analogous to the stream
method the IterableExtensions
class also provides a parallelStream
method.
It is also possible to map an iterable to a primitive iterable (see Primitve Iterables / From Iterables).
The JDK since Java 8 provides the class java.util.stream.Collector
which can be used with streams
to perform a reduction operation over all elements in a stream. The class java.util.stream.Collectors
already provides constructor methods for a bunch of useful collectors. The IterableExtensions
class
of this library provides a collect
extension method directly for Iterable
to easily reduce the elements
of the iterable.
Example:
import static java.util.stream.Collectors.*
import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
// ...
val Iterable<String> strings = #["fooooo", "baar", "baz"]
val summary = strings.collect(summarizingInt[length])
println("Average length: " + summary.average)
println("Max length: " + summary.max)
A fairly common task in Model-to-Model transformations is to find objects of one type that are not referenced by some other objects. This is usually done by navigation over all elements in a model, finding all elements of both types and then finding the elements that are actually not referenced. This is typically done by traversing the complete model twice to find the elements of both types. This can be an expensive operation if the input model is large. The solution is to traverse the model just once and group the elements according to their type.
This library provides a method allowing exactly this. The method groupIntoListBy
and groupIntoSetBy
groups elements of an iterable by their type.
Example:
val foo = "foo"
val bar = "bar"
val baz = "baz"
val traverseMe = #[foo, #[bar], baz, #[foo], bar]
val groups = traverseMe.groupIntoSetBy(String, List)
val Set<String> strings = groups.get(String)
val Set<List> lists = groups.get(List)
val inNoList = strings
.filter[str|
!lists.exists[it.contains(str)]
].toList
println("Elements contained in no list: " + inNoList)
To exclude elements of one Iterable
from another, the method withoutAll
can be used.
val s = #["I", "boo", "pity", "char", "the", "fool"]
.withoutAll(#{"boo", "char"})
.join(" ")
println(s)
Caution
|
Performance of withoutAll is best when using an appropriate java.util.Set , such as HashSet for the elements to exclude.
|
To partition the elements of an Iterable
based on a predicate or the class (elements are tested to be instance of) into two parts:
selected and rejected. The selected part will contain the elements for which the predicate evaluates to true
or elements are instance
of the given partitioning class. The rejected part will contain the other elements.
Example:
import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
// ...
val char[] chars = #[0x0020 as char, 0x0034 as char]
val List<CharSequence> list = #[
"Hello",
new StringBuilder().append(chars),
"Xtend",
new StringBuilder().append(0x0032 as char)
]
list.partitionBy(String) => [
println('''Selected: "«selected.join(" ")»"''')
println('''Rejected: "«rejected.join("")»"''')
]
For both versions of the partitionBy
method there exist overloads that take instances of Collector
.
Example:
import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
import static java.util.stream.Collectors.*
import java.util.Set
import java.util.List
// ...
val list = #["foo", "bla", "foo", "hui", "fun"]
val partition = list.partitionBy([startsWith("f")], toSet, toList)
val Set<String> selected = partition.selected
val List<String> rejected = partition.rejected
println("Unique words starting with 'f' : " + selected.join(", "))
println("Other words: " + rejected.join(", "))
Note that the Collectors#partitioningBy
Collector from the JDK aggregates into a Map<Boolean,List<T>>
where T
is the type of the elements in a collected stream. Another partitioningBy
overload from Collectors
aggregates the map values and returns a Map<Boolean,D>
where D
is the aggregation of a "downstream" Collector
.
import static java.util.stream.Collectors.*
import java.util.Set
// ...
val list = #["foo", "bla", "foo", "hui", "fun"]
val partition = list.stream.collect(partitioningBy([startsWith("f")], toSet))
val Set<String> selected = partition.get(true)
val Set<String> rejected = partition.get(false)
println("Unique words starting with 'f' : " + selected.join(", "))
println("Other words: " + rejected.join(", "))
To allow a similar workflow to the JDK version, for the Partition
of this library a asMap
extension method is provided
for Partitions
having the same type for the selected and rejected part. The other way around an extension method is provided
to wrap a Map<Boolean,X>
into a Partition<X,X>
.
To add elements from an Iterable
to one or more collections, the into
extension method is provided by the class IterableExtensions
.
Example:
val namesWithB = newArrayList("Barbara", "Bob", "Brian")
val newNames = #["Justin", "Anna", "Bruce", "Chris", "Becky"]
newNames
.filter[it.toFirstLower.startsWith("b")]
.into(namesWithB)
namesWithB.forEach[
println(it)
]
Tip
|
Related JavaDocs: |
Extensions to Iterator
To map from an Iterator (over references) to a PrimitiveIterator
s,
the class de.fhg.fokus.xtensions.iteration.IteratorExtensions
provides the methods mapInt
, mapLong
and mapDouble
.
import static java.util.stream.Collectors.*
import static extension de.fhg.fokus.xtensions.iteration.IteratorExtensions.*
// ...
val Iterable<String> strings = #["fooooo", "baar", "baz"]
val summary = strings.iterator.mapInt[length].summarize
println("Size of longest string is " + summary.max)
Tip
|
See Primitive Iterators for more information on the summarize extension function.
|
To exclude elements of one Iterable
from the sequence provided by an Iterator
, the method withoutAll
can be used.
val s = #["I", "boo", "pity", "char", "the", "fool"]
.iterator
.withoutAll(#{"boo", "char"})
.join(" ")
println(s)
Caution
|
Performance of withoutAll is best when using an appropriate java.util.Set , such as HashSet for the elements to exclude.
|
To partition the elements provided by an iterator based on a predicate or the class (elements are tested to be instance of) into two parts:
selected and rejected. The selected part will contain the elements for which the predicate evaluates to true
or elements are instance
of the given partitioning class. The rejected part will contain the other elements.
Example:
import static extension de.fhg.fokus.xtensions.iteration.IteratorExtensions.*
// ...
val char[] chars = #[0x0020 as char, 0x0034 as char]
val List<CharSequence> list = #[
"Hello",
new StringBuilder().append(chars),
"Xtend",
new StringBuilder().append(0x0032 as char)
]
list.iterator.partitionBy(String) => [
println('''Selected: "«selected.join(" ")»"''')
println('''Rejected: "«rejected.join("")»"''')
]
For both versions of the partitionBy
method there exist overloads that take instances of Collector
.
Example:
import static extension de.fhg.fokus.xtensions.iteration.IteratorExtensions.*
import static java.util.stream.Collectors.*
import java.util.Set
import java.util.List
// ...
val list = #["foo", "bla", "foo", "hui", "fun"]
val partition = list.iterator.partitionBy([startsWith("f")], toSet, toList)
val Set<String> selected = partition.selected
val List<String> rejected = partition.rejected
println("Unique words starting with 'f' : " + selected.join(", "))
println("Other words: " + rejected.join(", "))
Note that the Collectors#partitioningBy
Collector from the JDK aggregates into a Map<Boolean,List<T>>
where T
is the type of the elements in a collected stream. Another partitioningBy
overload from Collectors
aggregates the map values and returns a Map<Boolean,D>
where D
is the aggregation of a "downstream" Collector
.
val list = #["foo", "bla", "foo", "hui", "fun"]
val partition = list.iterator.partitionBy([startsWith("f")], Collectors::toSet, Collectors::toList)
val Set<String> selected = partition.selected
val List<String> rejected = partition.rejected
println("Unique words starting with 'f' : " + selected.join(", "))
println("Other words: " + rejected.join(", "))
To allow a similar workflow to the JDK version, for the Partition
of this library a asMap
extension method is provided
for Partitions
having the same type for the selected and rejected part. The other way around an extension method is provided
to wrap a Map<Boolean,X>
into a Partition<X,X>
.
To add elements from an Iterator
to one or more collections, the into
extension method is provided by the class IterableExtensions
.
Example:
val namesWithB = newArrayList("Barbara", "Bob", "Brian")
val newNames = #["Justin", "Anna", "Bruce", "Chris", "Becky"]
newNames.iterator
.filter[it.toFirstLower.startsWith("b")]
.into(namesWithB)
namesWithB.forEach[
println(it)
]
Tip
|
Related JavaDocs: |
Primitive Iterables
The JDK provides a generic java.util.Iterator<T>
interface and
primitive versions of the Iterator in form of the sub-interfaces of
java.util.PrimitiveIterator<T,T_CONS>
. However,
there are no primitive versions of the java.lang.Iterable<T>
interface, constructing primitive iterators.
So the JDK is missing an interface to abstract over "a bunch" of primitive numbers to iterate over. A primitive iterator or primitive
stream can only traversed once, which is not very satisfying in many cases. Ideally there should be in interface allowing the
iteration over a (possibly infinite) sequence of primitive numbers. We want to be able to get a primitive iterator, a primitive
stream, or directly iterate over the elements with a forEach
method. A set of these interfaces is provided in package
de.fhg.fokus.xtensions.iteration
.
The primitive Iterable versions provided in the package all specialize java.lang.Iterable
with the boxed
number type, but also provide specialized functions for providing primitive iterators, primitive streams, and
forEach methods that do not rely on boxing the primitive values when passing them on to the consumer.
In the following sections we will explore the ways to create those primitive Iterables. Primitive Iterables can be created …
Examples for usage of primitive Iterables:
import java.util.PrimitiveIterator
import static extension de.fhg.fokus.xtensions.iteration.IntIterable.*
// ...
def printHex(IntIterable ints) {
ints.forEachInt [
val hex = Integer.toHexString(it)
println(hex)
]
}
def printHex(IntIterable ints, int limit) {
val PrimitiveIterator.OfInt iter = ints.iterator
for(var counter = 0; iter.hasNext && counter < limit; counter++) {
val i = iter.nextInt
val hex = Integer.toHexString(i)
println(hex)
}
}
def printHexOdd(IntIterable ints) {
val IntStream s = ints.stream.filter[it % 2 == 1]
s.forEach [
val hex = Long.toHexString(it)
println(hex)
]
}
From Iterables
Iterables can be mapped to primitive iterables by the special map extension functions mapInt
, mapLong
and mapDouble
defined in de.fhg.fokus.xtensions.iteration.IterableExtensions
.
Example:
import static extension de.fhg.fokus.xtensions.iteration.IterableExtensions.*
import de.fhg.fokus.xtensions.iteration.IntIterable
// ...
val IntIterable lengths = newArrayList("foo", "baaaar", "bz").mapInt[length]
From Arrays
The asIntIterable
extension method method creates a primitive iterable for primitive arrays.
There are two versions: One version creates an iterable over the complete array, the other one produces
an iterable over a section of the array. The section can be specified by defining the start index and
an excluding end index.
Example:
import static extension de.fhg.fokus.xtensions.iteration.PrimitiveArrayExtensions.*
import de.fhg.fokus.xtensions.iteration.IntIterable
// ...
val int[] arr = #[0,2,4,19,-10,10_000,Integer.MAX_VALUE,Integer.MIN_VALUE]
var IntIterable ints = arr.asIntIterable(1, arr.length - 1) // omit first and last element
From Computations
In following we are using IntIterable to show how to create computed primitive iterables, but respective factory methods are also available on LongIterable and DoubleIterable.
To create an IntIterable representing an infinite number of int values the static generate
factory method can be used. This method has to provided with a function which itself provides
an IntSupplier
. The function will be called each time a PrimitiveIterator.OfInt
is needed or an IntStream
is created from the IntIterable
.
Example:
import de.fhg.fokus.xtensions.iteration.IntIterable
// ...
val IntIterable ints = IntIterable.generate [
val rand = new Random;
[rand.nextInt]
]
For IntIterables of infinite int values that can be simply computed from a
seed value and a mapping function from the previous to the next value, the
iterate
factory method can be used. The seed value provided will be returned
as the first element of the iterable.
Example:
import de.fhg.fokus.xtensions.iteration.IntIterable
// ...
val IntIterable ints = IntIterable.iterate(1)[it * 2]
If a finite IntIterable is needed that can be constructed similar to the classical
for-loop, the iterate
method with three parameters can be used. The first argument
defines the first (seed) value , the second argument defines the termination condition.
While this condition holds a next value is provided. If the condition does not hold
for the initial value, an empty IntIterable is created.
The third argument defines the function calculating the next value from the previous one.
Example:
import de.fhg.fokus.xtensions.iteration.IntIterable
// ...
val IntIterable ints = IntIterable.iterate(0, [it<=10], [it+2])
// will provide values 0, 2, 4, 6, 8, and 10
From Xtend Ranges
Creating iterables from org.eclipse.xtext.xbase.lib.IntegerRange
can be done via the extensions
class de.fhg.fokus.xtensions.range.RangeExtensions
.
Example:
import static org.eclipse.xtext.xbase.lib.IntegerRange.*
// ...
val IntIterable iter = (0..50).withStep(2).asIntIterable
Creating an iterable from an org.eclipse.xtext.xbase.lib.ExclusiveRange
is currently not supported
due to the public API limitations on that class.
From Primitive Optionals
The extension classes for primitive Optionals allow the creation of primitive iterables allowing iteration over either one or no value, depending on the source Optional.
Example:
import static extension de.fhg.fokus.xtensions.optional.OptionalIntExtensions.*
// ...
val IntItreable ints = some(42).asIterable
Tip
|
Related JavaDocs: |
Extensions to PrimitiveIterators
The primitive iterators defined in the JDK as sub-interfaces of java.util.PrimitiveIterator
do not provide combinators like the ones provided by Xtend. These combinators, however, do take some
efforts to implement. Instread, this library provides the class
de.fhg.fokus.xtensions.iteration.PrimitiveIteratorExtensions
provides methods to
create primitive streams (from java.util.stream
) for the remaining elements of a given iterator via the
extension methods streamRemaining
or parallelStreamRemaining
. Note that the method streamRemaining
does not guarantee that the elements provided by the returned stream are actually taken from the originating
iterator. If the underlying iterator implementation is known, the framework may construct a stream that may have
better characteristics in some way. If elements should actually be removed from the originating iterator, the
streamRemainingExhaustive
method can be used.
To create a summary object providing the minimum value, maximum value, average value, sum value, and count of elements,
the PrimitiveIteratorExtensions
class provides a summarize
function for all three primitive iterators.
Example:
val range = 1..100
val summary = range.intIterator.summarize
println('''Sum of elements in range [«range.start»..«range.end»] is «summary.sum»''')
Tip: If you want to know more about the extension methods available on IntegerRange
, have a look at chapter Extensions to IntegerRange
Tip
|
Related JavaDocs: |
Extensions to String
The class de.fhg.fokus.xtensions.string.StringSplitExtensions
provides extension methods
for java.lang.String
allowing to lazily split a string value.
The extension method splitIt
returns an Iterator
which lazily performs string split
operations based on a regular expression (same String#split(String)
) would do, but
lazily. This allows the use of Iterator extension methods provided by Xtend and to stop splitting
a string when a condition is met without splitting the complete input string beforehand.
Example:
import static extension de.fhg.fokus.xtensions.string.StringSplitExtensions.*
// ...
val Iterator<String> i = "foozoobaar".splitIt("(?<=oo)")
i.takeWhile[!startsWith("b")].forEach[
println(it)
]
Tip
|
If a split pattern is known in advance the following is possible with the JDK types to obtain a Stream of split elements:
|
If a pattern String has to be produced dynamically, the extension method splitAsStream
is provided
as a shortcut for the sequence of calls from above:
import static extension de.fhg.fokus.xtensions.string.StringSplitExtensions.*
// ...
val String patternStr = ... // dynamically created pattern
"tosplit".splitAsStream(patternStr)
The class de.fhg.fokus.xtensions.string.SptringMatchExtensions
provides extension methods to
java.lang.String
, allowing to match regular expressions lazily via iterators.
To manually get matches for a pattern from an input string with JDK classes the following sequence has to be used:
import java.util.regex.Pattern
// ...
val String input = "foo bar boo"
val Pattern pattern = Pattern.compile("(\\woo)")
val matcher = pattern.matcher(input)
while(matcher.find) {
val match = input.subSequence(matcher.start, matcher.end)
// Do something with match
println(match)
}
The extension method matchIt
elegantly wraps this usage pattern into an Iterator, so the Xtend combinators
can be used on them.
import static extension de.fhg.fokus.xtensions.string.StringMatchExtensions.*
import java.util.regex.Pattern
// ...
val String input = "foo bar boo"
val Pattern pattern = Pattern.compile("(\\woo)")
input.matchIt(pattern).forEach [
println(it)
]
The method matchIt
is overloaded to also take a string of the pattern, which internally compiles
it to a pattern.
Having a stream of MatchResult
s
for a pattern applied to a given input string can be achieved with the matchResultIt
extension method.
This can be useful, if other group captures have to be accessed when handling matches.
Tip
|
Related JavaDocs: |
Extensions to Duration
The class de.fhg.fokus.xtensions.datetime.DurationExtensions
provides static extension
method for the JDK class java.time.Duration
Since Java does not allow operator overloading, the Duration class provides many methods with names
corresponding to operators, like plus
, minus
, dividedBy
, multipliedBy
, and negated
.
Since Xtend does allow operator overloading for the corresponding operators, aliases for the operators
+
, -
, /
, *
, and unary -
are defined.
The Duration class also provides static factory methods for durations of a given time units
(e.g. Duration ofNanos(long nanos)
).
To make these constructions more easy to read, the DurationExtensions
class provides extension methods
to the long
type.
Example:
import static extension de.fhg.fokus.xtensions.datetime.DurationExtensions.*
import java.time.Duration
// ...
val Duration twoPointFiveSeconds = 2.seconds + 500.milliseconds
Tip
|
Related JavaDocs: |
Extensions to Functions
Xtend provides own functional interfaces in the
org.eclipse.xtext.xbase.lib.Functions
Interface. These are used all over the Xtend standard library and they allow a compact declaration syntax, e.g. the type
Function1<? super String,? extends String>
can be written as (String)⇒String
.
Extensions to Xtends functional interfaces are provided in de.fhg.fokus.xtensions.function.FunctionExtensions
.
This library’s FunctionExtensions
provides another overload of the method andThen
which allows composition of a
()⇒T
function with a (T)⇒U
function, resulting in a composed ()⇒U
function.
Example:
import static extension de.fhg.fokus.xtensions.function.FunctionExtensions.*
import java.time.LocalDate
// ...
val ()=>LocalDate inOneYear = [LocalDate.now.plusYears(1)]
val (LocalDate)=>String yearString = [it.year.toString]
val ()=>String nextYear = inOneYear.andThen(yearString)
println(nextYear.apply)
Inspired by the |>
operator of F# and Elixir, this library introduces the >>>
operator,
which can be seen as a "pipe through" operator. It takes the value of the left hand side and
calls the function on the right hand side with the value. This means that
val (X)=>Y f = ...
val X x = ...
x >>> f
// equal to
f.apply(x)
This is especially handy when having to call several functions in a row,
so a.apply(b.apply(x))
can be written as x >>> b >>> a
.
It can also be useful to transforming transform the value returned by a method call
before assigning it to a final variable without having to define a separate method.
It can also be used like the ⇒
operator (to have a value as a context value it
)
just with a different return value.
Example:
import static extension de.fhg.fokus.xtensions.function.FunctionExtensions.*
import java.nio.file.Paths
// ...
val path = System.getProperty("user.home") >>> [Paths.get(it)]
println(path.parent)
The >>>
operator is overloaded to also destructure a Pair
value into key
and value
on call.
This means that the left hand side of the operator must be evaluated to a value of type Pair and the
right hand side of the operator must be a function with two parameters of the types of key and value of
the Pair (K,V)⇒Y
.
Example:
import static extension de.fhg.fokus.xtensions.function.FunctionExtensions.*
// ...
val list = #["foo", "bar", "foo", "baz", "foo", "bar"]
list.splitHead
>>> [head,tail| head -> tail.toSet.size]
>>> [head,remaining| '''Head: "«head»", remaining: «remaining» unique elements''']
>>> [println(it)]
// ...
def <T> Pair<T,Iterable<T>> splitHead(Iterable<T> elements) {
elements.head -> elements.tail
}
To compose functions, the shortcut operators >>
for andThen
and <<
for compose
were introduced.
Example:
import static extension de.fhg.fokus.xtensions.function.FunctionExtensions.*
import java.time.LocalDate
// ...
val (LocalDate)=>LocalDate oneYearLater = [it.plusYears(1)]
val (LocalDate)=>String yearString = [it.year.toString]
val (LocalDate)=>String yearAfter = oneYearLater >> yearString
LocalDate.now >>> yearAfter >>> [println(it)]
When working with the Xtend extension methods on Iterator
and Iterable
sometimes
(X)⇒Boolean
types are needed, e.g. for the exists
and filter
combinator.
Unfortunately the Xtend boolean functions do not have the composition functions as the
Java 8 java.util.function.Predicate
interface. This library’s FunctionExtensions
class does provides the equivalent methods and
, or
, and negate
.
import static extension de.fhg.fokus.xtensions.function.FunctionExtensions.*
// ...
val (String)=>boolean notThere = [it.nullOrEmpty]
val (String)=>boolean tooShort = [it.length < 3]
val (String)=>boolean valid = notThere.or(tooShort).negate
#["ay", "caramba", null, "we", "fools"]
.filter(valid)
.forEach[
println(it)
]
Tip
|
Related JavaDocs: |
Extensions to CompletableFuture
Some might complain that the java.util.concurrent.CompletionStage
/java.util.concurrent.CompletableFuture
API surface is too large and difficult to wrap your head around. But actually many methods are similar and certain
use cases are verbose to express with the given methods. Therefore we provide a couple of extension methods to
make certain actions more convenient on CompletableFuture
. These extension methods are provided via the class
de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions
.
The first thing one usually notices is that there are three methods that to handle the success case case
on CompletableFuture
: thenApply
, thenAccept
, and thenRun
. These methods are only named
differently, because the Java compiler cannot figure out which functional interface a lambda is conforming
to if a method is overloaded with two or more versions with different functional interface parameters.
Interestingly Xtend does not have this restrictions and can figure out pretty well which overloaded version
of a method is called, based on inspection of the lambda passed to the method.
Therefore the CompletableFutureExtensions
class provides then
methods simply redirecting to the
JDK methods.
Example:
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
// ...
val pool = Executors.newSingleThreadExecutor
val fut = CompletableFuture.supplyAsync([
new Random().nextInt(1000)
],pool).then [ // thenApply, since has input and output value
it / 10.0
].then [ // thenAccept, since has input, but expression does not return value
System.out.println('''Random percent: «it»''')
].then [| // thenRun, since lambda does not take input
System.out.println("The end.")
]
Tip
|
You may have noticed that the syntax for spawning a supplier via CompletableFuture#supplyAsync on a custom executor does not look elegant, since the pool parameter is the last one. So the lambda cannot be written behind the closing parenthesis of the parameter list. Have a look at the section Async Computations for a more Xtend style API. |
Note
|
Currently there there are no thenAsync versions of the then methods implemented, but they are
planned to be provided in the future.
|
The extension methods starting with when
register a callback on a CompletableFuture
which is invoked
when it is completed an in a certain state, depending on the method. The returned future will always be completed
with the original value (successfully or exceptionally), except if the callback throws an exception. In this case
the returned future will be completed exceptionally with the exception thrown by the callback. If the callback
is registered before completion of the future, the callback is invoked on the thread completing the future. If
the callback is registered after completion of the future, the callback is invoked on the thread registering
the callback. The async version of the when
methods are always completed on the executor passed to the
method, or on the common ForkJoinPool
for the async version which does not take an executor as argument.
The extension method whenCancelled
allows registering a callback on a CompletableFuture
. The callback is
invoked when the future was completed via cancellation.
Example:
import java.util.concurrent.CompletableFuture
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
val toCancel = new CompletableFuture
toCancel.whenCancelled [|
println("I've been canceled")
]
toCancel.cancel
The method whenException
registers a callback which is invoked when the future is completed exceptionally.
Example:
import java.util.concurrent.CompletableFuture
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
CompletableFuture.supplyAsync [
throw new IllegalStateException
].whenException [
println('''failed with «it.class» and cause «it.cause.class»''')
]
The recoverWith
extension method is similar to the thenCompose
method, but for the exceptional case.
The registered callback of type (Throwable)⇒CompletionStage<? extends R>
will be invoked if the future
the callback is registered on completes exceptionally. The callback will be called with the exception the
original future was completed with exceptionally. The future returned from the callback will be used to
complete the future returned from the recoverWith
extension method. This means if the original future
completes successfully, the result will be used to complete the future returned from the recoverWith
method. Otherwise the result of the recovery callback will be forwarded to the overall result future
(no matter if the result is successful or exceptional).
Example:
import java.util.concurrent.CompletableFuture
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
CompletableFuture.supplyAsync [
throw new IllegalStateException("Boom!")
].recoverWith [
if(it.cause instanceof IllegalStateException)
CompletableFuture.supplyAsync [
"I was expecting you! Here is your asynchronous backup value."
]
else
throw new IllegalArgumentException("Did not expect this!", it)
].thenAccept [
println(it)
]
There are also recoverWithAsync
versions where the recovery callback will always be executed on a given
executor.
It may be useful to abort a computation and get a default value instead. This can be done using the
handleCancellation
extension method and canceling the original future.
The handleCancellation
extension method is called with a supplier function which provides a result
value when the source future is cancelled. If the original future completes successfully, the returned
future will simply be completed with the same value. If the original future was cancelled (or completed
with a java.util.concurrent.CancellationException
), the given callback is called. If the callback
completes successfully, the result will be set on the resulting future. If the callback throws an exception,
this exception will be set as exceptional result to the resulting future. If the original future was completed
exceptionally with a different exception, the same exception will be set as the exceptional result
to the returned future.
import java.util.concurrent.CompletableFuture
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
val lateVal = CompletableFuture.supplyAsync [
// Do not do this at home!
// We are blocking the common pool
Thread.sleep(1000)
"here is some belated value."
]
lateVal.handleCancellation [
"Here is some default value."
].thenAccept [
println(it)
]
// let's be impatient
lateVal.cancel
The handleCancellationAsync
variant executes the given handler always on the a provided executor.
Sometimes it is needed to take the result of one CompletableFuture
and forward the result to another
future. This can e.g. be needed when a function is handed a future to complete and gets the actual result
from a method returning a future. For cases like this the forwardTo
extension method can be used.
Example:
import java.util.concurrent.CompletableFuture
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
def void completeWithResult(CompletableFuture<String> res, boolean heavy) {
if(heavy){
doSomeHeavyWork().forwardTo(res)
} else {
res.complete("Some light work")
}
}
def CompletableFuture<String> doSomeHeavyWork() {
CompletableFuture.supplyAsync [
"Did some heavy lifting"
]
}
When returning a CompletableFuture
from a method it may make sense to not return the future itself,
but a copy, which will be completed
When returning a CompletableFuture
from a method which is decoupled from one ore more internal
futures (e.g using the copy
or forwardTo
extension method) it may still make sense to forward
cancellation from the returned future to the futures used internally to abort sub-tasks.
Example:
import java.util.concurrent.CompletableFuture
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
def CompletableFuture<String> someCancellableComposition(Executor executor) {
val result = new CompletableFuture<String>
val CompletableFuture<String> firstStep = firstStep(executor)
result.forwardCancellation(firstStep)
firstStep.thenCompose [
val secondStep = secondStep(executor,it)
result.forwardCancellation(secondStep)
secondStep
].forwardTo(result)
result
}
def CompletableFuture<String> firstStep(Executor executor) {
val result = new CompletableFuture<String>
executor.execute [|
Thread.sleep(10) // evil!
if(result.cancelled) {
println("cancelled in first step")
} else {
result.complete("Some result")
}
]
result
}
def CompletableFuture<String> secondStep(Executor executor, String input) {
val result = new CompletableFuture<String>
executor.execute [|
if(result.cancelled) {
println("cancelled in first step")
} else {
val output = input.toUpperCase
result.complete(output)
}
]
result
}
As you see in the example, the cancellation is forwarded to the two futures that are composed to calculate the overall result. Yet the returned future cannot be used to complete any internal future with a bogus result value.
The extension method cancelOnTimeout
is canceling a given CompletableFuture
when a timeout occurs. Note that this method returns the same future that is passed in.
This method does not return a new future, consider the complex form of orTimeout
(see below) for this effect.
Example:
CompletableFuture.supplyAsync [
Thread.sleep(100) // Never actually do this!
"Wow, so late"
].cancelOnTimeout(50, TimeUnit.MILLISECONDS)
.whenCancelled[|
println("Oh no! It took too long.")
]
Alternatively, a version of cancelOnTimeout
is provided taking a java.time.Duration
as parameter.
Sometimes blocking APIs have to be used, but a future based API should be provided to the user.
In this case it may be desirable that the user can cancel the future to interrupt the thread
performing a blocking operation. This is tricky when running the blocking operations
using a thread pool, since the thread should only be interrupted as long as the operation
associated with the future is running. To support this use case the whenCancelledInterrupt
method is provided.
Example:
val blockOpPool = Executors.newCachedThreadPool // pool for running blocking operations
/// ...
val sleepy = blockOpPool.asyncRun [ CompletableFuture<?> it | (1)
it.whenCancelledInterrupt [|
try {
Thread.sleep(100) // perform blocking operation
} catch (InterruptedException e) {
println("Hey, I was cancelled")
}
]
]
// ...
sleepy.cancel // may interrupt Thread.sleep
-
Here an extension method described in Async Computations is used.
The following functions introduced in JDK 9 on CompletableFuture
have been back-ported
in class de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions
as extension methods:
Note, there is also a overloaded version of orTimeout
which allows more fine grained options on the behavior of
this method. Here is an example for the configuration options:
val slowFut = CompletableFuture.supplyAsync [
Thread.sleep(100) // Never actually do this!
"Phew, so late"
]
val withTimeout = slowFut.orTimeout [
backwardPropagateCancel = false // do not cancel slowFut if withTimeout is cancelled
cancelOriginalOnTimeout = false // do not cancel slowFut on timeout
exceptionProvider = [new TimeoutException] // exception used to complete withTimeout on timeout
scheduler = new ScheduledThreadPoolExecutor(1) // scheduler used for timeout
timeout = (50L -> TimeUnit.MILLISECONDS) // time after which withTimeout is completed exceptionally
tryShutdownScheduler = true // if true tries to shutdown the given scheduler when slowFut completes
]
Tip
|
Related JavaDocs: |
Async Computations
Starting asynchronous computations and providing the result via a CompletableFuture
is
provided via the JDK methods CompletableFuture#runAsync
and CompletableFuture#suppyAsync
.
These methods have a few drawbacks. The first one is that in Xtend it is good practice to place the callback function as the last parameter in a parameter list to allow for more elegant and readable syntax, placing the lambda behind the closing parentheses. The JDK methods, however, have overloaded versions placing a executor for operation executor as last parameter.
The other drawback is that these methods need a further concept to allow cancellation of an operation
from the caller side, e.g. when the user cancels an operation. This can e.g. be achieved via an
additional java.util.concurrent.atomic.AtomicBoolean
which is passed to the operation.
This is unfortunate, since the CompletableFuture
already knows the concept of cancellation.
This library provides the class de.fhg.fokus.xtensions.concurrent.AsyncCompute
introducing
the methods asyncRun
and asyncSupply
. These methods allow asynchronous computations
like the JDK methods, but with a shuffled parameter list and passing the created CompletableFuture
into the operation to be computed asynchronously.
Example using JDK classes:
import static java.util.concurrent.CompletableFuture.*
import java.util.concurrent.Executors
// ...
val ex = Executors.newCachedThreadPool
val isCancelled = new AtomicBoolean(false)
runAsync([
if(isCancelled.get) {
println("Oh no, I've been cancelled")
} else {
println("I'm fine")
}
], ex)
isCancelled.set(true)
Same example using AsyncCompute
:
import static extension de.fhg.fokus.xtensions.concurrent.AsyncCompute.*
import java.util.concurrent.Executors
// ...
val pool = Executors.newCachedThreadPool
val fut = pool.asyncRun [
if(cancelled) {
println("Oh no, I've been cancelled")
} else {
println("I'm fine")
}
]
fut.cancel(false)
The asyncRun
and asyncSupply
methods have variants defining a timeout. If the provided actions
do not complete in the defined timeout, the returned future will be completed with a TimeoutException
.
The action should then check for completion of the future passed into it, instead of for cancellation.
Example:
asyncSupply(10, TimeUnit.MILLISECONDS) [
// some poor integration approximation
val fx = [double x| x*x + 10 - (2*x)]
var sum = 0.0d;
for(i : 0..100_000) {
// every now and then, check if we timed out
if(i % 100 == 0) {
if(done) {
return 0.0d;
}
}
// also using poor double accumulation
sum += fx.apply(i as double)
}
sum
].whenComplete[result, error |
if(error !== null) {
println("Whoops, timeout")
} else {
println("result = " + result)
}
]
Tip
|
Related JavaDocs: |
Scheduling Util
The class de.fhg.fokus.xtensions.concurrent.SchedulingUtil
provides several static methods
and static extension methods to easily schedule action for deferred or repeated execution.
All operations have overloaded variants taking a java.util.concurrent.ScheduledExecutorService
as the first parameter, so these methods can be used as extension methods.
To repeat an action with a given period of time (starting immediately) you can use one of the overloaded
versions of the repeatEvery
method.
Example:
import static extension de.fhg.fokus.xtensions.concurrent.SchedulingUtil.*
import static extension de.fhg.fokus.xtensions.datetime.DurationExtensions.*
// ...
val hundredMs = 100.milliseconds
repeatEvery(hundredMs) [
println(currentTimeMillis)
]
To repeat an action with a given period, starting with a delay instead of immediately, an overloaded
version of the repeatEvery
method can be used:
Example:
import static extension de.fhg.fokus.xtensions.concurrent.SchedulingUtil.*
import java.util.concurrent.TimeUnit
// ...
repeatEvery(100, TimeUnit.MILLISECONDS).withInitialDelay(200) [
println("Delayed start, repeated every 100 milis period")
]
Note that the action will stop being repeatedly called if the action throws an exception or the future
returned by the repeatEvery
method will be completed (e.g. by canceling it). This can either either
be done by the action itself (the future will be passed to the action as parameter), or from the outside.
Since the future is both passed to the action and returned, this also allows the action to check e.g. for
cancellation from the outside and aborting the action early.
import static extension de.fhg.fokus.xtensions.concurrent.SchedulingUtil.*
import static extension de.fhg.fokus.xtensions.datetime.DurationExtensions.*
// ...
val hundredMs = 100.milliseconds
val fut = repeatEvery(hundredMs) [
for(i : 0..Integer.MAX_VALUE) {
if(cancelled) {
println("I've been cancelled at iteration " + i)
return
}
}
]
fut.cancel(false)
The method delay
will defer the one-time execution of a given action by the given duration.
The delayed execution can be aborted before being started by completing the future returned by
the delay
method.
The future returned by the delay
method is also passed as a parameter to the deferred action.
If the future is completed before the delay is expired, the action will not be executed. If the
action is performed, it can check during execution if the future is completed, e.g. to return
prematurely (abort the action early).
import static extension de.fhg.fokus.xtensions.concurrent.SchedulingUtil.*
import static extension de.fhg.fokus.xtensions.concurrent.CompletableFutureExtensions.*
// ...
val result = new CompletableFuture<String>
result.thenAccept [
println(it)
]
Executors.newCachedThreadPool.submit [
Thread.sleep(100)
result.complete("late response")
]
delay(50.milliseconds) [
"default value"
].forwardTo(result) (1)
-
This extension method is explained in Extensions to CompletableFuture
The method waitFor
will create a CompletableFuture
that will be completed successfully
with a null
value when the given duration expires. An overloaded version of the waitFor
method allows a deferred execution of a given callback, similar to the delay
method, but
the callback does not provide a return value. The returned future will be completed with a
null
value after successful execution.
import static extension de.fhg.fokus.xtensions.concurrent.SchedulingUtil.*
//...
val repeatingFut = repeatEvery(100, TimeUnit.MILLISECONDS).withInitialDelay(50) [
println("Delayed start, repeated every 100 milis period")
]
waitFor(50.milliseconds) [
repeatingFut.cancel(false)
]
The same effect as shown here can be achieved with the cancelOnTimeout
extension method on CompletableFuture,
described in Extensions to CompletableFuture.
Tip
|
Related JavaDocs: |
Primitives
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
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:
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
person?.address.boxNum[floor]
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.
It makes sense to call onNull
functions subsequent to the boxing functions as described in Default Values for 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:
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 check a Boolean
for it’s value in a null
-aware manner and directly act on the test,
the methods ifTrue
, ifFalse
, ifNullOrTrue
, and ifNullOrFalse
can be used.
import static extension de.fhg.fokus.xtensions.primitives.Primitives.*
// ...
val person = loadPerson("Mike")
person?.address.box[isValidated].ifTrue [
println("Address validated")
]
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:
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
, optionalDouble
and
optionalBool
functions are supposed to box a primitive value property of a context object into a primitive 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.
Example:
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 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
the unboxed primitive value.
Example:
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
|
Related JavaDocs: |
Objects
The class de.fhg.fokus.xtensions.objects.Objects
provides a bunch of static extension methods
that can generally help with handling objects especially in null-safe navigation chains.
Recovering from null
To check if an object is null
and provide a fallback object if the tested object is null
,
the Objects
class provides the extension functions recoverNull(T,T)
and recoverNull(T,⇒T)
.
The Java 9 JDK provides functions the same functionality via the methods java.util.Objects#requireNonNullElse(T,T)
and
java.util.Objects#requireNonNullElseGet(T, Supplier<? extends T>)
. This library provides API compatible
aliases for the recoverNull
functions, so that a migration to Java 9 and the built-in methods is smooth.
Example:
import static extension de.fhg.fokus.xtensions.objects.Objects.*
// ...
val person = new Person(null, null)
val first = person.getFirstName.recoverNull("John")
val last = person.getLastName.recoverNull[if(first == "John") "Doe" else "Unknown"]
println('''«first» «last»''')
In this example we initialize a person with first and last name set to null
in both cases the recoverNull
functions have to provide a fallback object. Since first name defaults to "John" the last name will be evaluated
to "Doe".
Acting on non-null
The extension function ifNotNull
will check a context object on null
and if it is not will pass
the object to a consumer procedure. This can be handy at the end of null-safe navigation chains.
Example:
import static extension de.fhg.fokus.xtensions.objects.Objects.*
// ...
val person = // ...
preson?.getFirstName?.toFirstUpper.ifNotNull [
println(it)
]
Cast or null
The Xtend operator as
tests if an object instance of a given class and casts the objec
to this type if so. If not, the result of the operator will be null
. Unfortunately this
cannot be simply used in a (null-safe) navigation chain. The asType
extension method
wraps the same semantics in a function that can be called as part of a call chain.
Example:
val Vehicle v = // ...
v.asType(Car)?.getMake.ifNotNull [
println("Make: " + it)
]
In the example the vehicle is casted to a Car and afterwards is either of type Car
or null
.
Therefore subsequent calls are performed via null-safe navigation. Since the object can now be
used as a car, methods of that type, such as getMake
can be accessed.
Tip
|
Related JavaDocs: |