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

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:

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
	}
}

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 (via fallback) on empty

  • <X extends Throwable> boolean orElseThrow(Supplier<? extends X> exceptionSupplier) throws X throws an exception provided by exceptionSupplier 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.

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;
}
  1. Here languages can be returned directly instead of languages.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)
}
  1. In this line the extension method stream is called on the iterable strings.

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 PrimitiveIterators, 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:

import java.util.regex.Pattern
// ...
extension val pattern = Pattern.compile("mypattern")
// ...
"tosplit".splitAsStream  // actually calls pattern.splitAsStream("tosplit")

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 MatchResults 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.

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
  1. 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)
  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:

results matching ""

    No results matching ""