diff --git a/.gitignore b/.gitignore index 0f182a0..3586a36 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.class - -# Package Files # *.jar -*.war -*.ear + +src/main/xtend-gen +src/test/xtend-gen +/target diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..27ab26a --- /dev/null +++ b/pom.xml @@ -0,0 +1,73 @@ + + 4.0.0 + + com.github.xtension + xtension + 0.1-SNAPSHOT + + + 2.4.2 + 14.0.1 + 1.6 + UTF-8 + + + + + com.google.guava + guava + ${guava.version} + + + org.eclipse.xtend + org.eclipse.xtend.lib + ${xtend.version} + + + junit + junit + 4.11 + test + + + org.assertj + assertj-core + 1.3.0 + test + + + + + + + maven-compiler-plugin + 3.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + + org.eclipse.xtend + xtend-maven-plugin + ${xtend.version} + + + + compile + testCompile + + + src/main/xtend-gen + src/test/xtend-gen + ${project.build.sourceEncoding} + + + + + + + diff --git a/src/main/java/com/github/xtension/ComparableExtensions.xtend b/src/main/java/com/github/xtension/ComparableExtensions.xtend new file mode 100644 index 0000000..1412451 --- /dev/null +++ b/src/main/java/com/github/xtension/ComparableExtensions.xtend @@ -0,0 +1,25 @@ +package com.github.xtension + +import com.google.common.annotations.Beta + +final class ComparableExtensions { + + private new() { + } + + /** + * Determines whether the value is in [start, end] range (start included, end included). + */ + @Beta + def static boolean isBetween(Comparable obj, C start, C end) { + obj.compareTo(start) >= 0 && obj.compareTo(end) <= 0 + } + + /** + * Determines whether the value value is in (start, end) range (start excluded, end excluded). + */ + @Beta + def static boolean isStrictlyBetween(Comparable obj, C start, C end) { + obj.compareTo(start) > 0 && obj.compareTo(end) < 0 + } +} \ No newline at end of file diff --git a/src/main/java/com/github/xtension/IterableExtensions.xtend b/src/main/java/com/github/xtension/IterableExtensions.xtend new file mode 100644 index 0000000..238ed8f --- /dev/null +++ b/src/main/java/com/github/xtension/IterableExtensions.xtend @@ -0,0 +1,330 @@ +package com.github.xtension + +import java.util.Comparator +import java.util.List +import java.util.Map +import com.google.common.base.Optional +import com.google.common.collect.AbstractIterator +import com.google.common.collect.FluentIterable +import com.google.common.collect.Iterables +import com.google.common.collect.Lists +import com.google.common.collect.Maps +import com.google.common.collect.Ordering +import org.eclipse.xtext.xbase.lib.internal.BooleanFunctionDelegate + +import static extension com.github.xtension.MapExtensions.* + +final class IterableExtensions { + + private new() { + } + + /** + * Returns an {@link Optional} containing the first element in this iterable. + * If the iterable is empty, {@code Optional.absent()} is returned. + * + * @throws NullPointerException if the first element is null; if this is a possibility, use + * {@link org.eclipse.xtext.xbase.lib.IterableExtensions#head} instead. + */ + def static Optional headOptional(Iterable iterable) { + FluentIterable::from(iterable).first + } + + /** + * Returns an {@link Optional} containing the last element in this fluent iterable. + * If the iterable is empty, {@code Optional.absent()} is returned. + * + * @throws NullPointerException if the last element is null; if this is a possibility, use + * {@link org.eclipse.xtext.xbase.lib.IterableExtensions#last} instead. + */ + def static Optional lastOptional(Iterable iterable) { + FluentIterable::from(iterable).last + } + + /** + * Returns an {@link Optional} containing the first element in this iterable that + * satisfies the given predicate, if such an element exists. + * + *

Warning: avoid using a {@code predicate} that matches {@code null}. If {@code null} + * is matched in this fluent iterable, a {@link NullPointerException} will be thrown. + */ + def static Optional findFirstOptional(Iterable iterable, (T) => boolean predicate) { + Iterables::tryFind(iterable, new BooleanFunctionDelegate(predicate)) + } + + /** + * Builds a new iterable by applying a function to all elements of this iterable + * and using the elements of the resulting iterables. + * + *

For example: + * + *

{@code val words = lines.flatMap[split("\\W+").toList]} + */ + def static Iterable flatMap(Iterable iterable, (T) => Iterable function) { + iterable.map(function).flatten + } + + /** + * Counts the number of elements in this iterable which satisfy a predicate. + */ + def static int count(Iterable iterable, (T) => boolean predicate) { + var count = 0 + for (element : iterable) { + if (predicate.apply(element)) { + count = count + 1 + } + } + + count + } + + /** + * Produces the range of all indices of this iterable. + * + * @return a range from 0 to one less than the size of this iterable. + */ + def static ExclusiveRange indices(Iterable iterable) { + 0 ..< iterable.size + } + + /** + * Returns an iterable whose {@code Iterator} cycles indefinitely over the elements of + * this iterable. + * + *

That iterator supports {@code remove()} if {@code iterable.iterator()} does. After + * {@code remove()} is called, subsequent cycles omit the removed element, which is no longer in + * this iterable. The iterator's {@code hasNext()} method returns {@code true} until + * this iterable is empty. + * + *

Warning: Typical uses of the resulting iterator may produce an infinite loop. You + * should use an explicit {@code break} or be certain that you will eventually remove all the + * elements. + */ + def static Iterable cycle(Iterable iterable) { + Iterables::cycle(iterable) + } + + /** + * Produces a new iterable which contains all elements of this iterable and also all elements of + * a given iterable. The source iterators are not polled until necessary. + * + *

The returned iterable's iterator supports {@code remove()} when the + * corresponding input iterator supports it. + */ + def static Iterable union(Iterable a, Iterable b) { + Iterables::concat(a, b) + } + + /** + * Returns an iterable formed from this iterable and another iterable by combining + * corresponding elements in pairs. If one of the two collections is longer than the other, + * its remaining elements are ignored. The source iterators are not polled until necessary. + * + *

The resulting iterable's iterator does not support {@code remove()}. + */ + def static Iterable> zip(Iterable a, Iterable b) { + val FluentIterable> result = [| + val iterator1 = a.iterator + val iterator2 = b.iterator + + val AbstractIterator> iterator = [| + if (iterator1.hasNext && iterator2.hasNext) { + iterator1.next -> iterator2.next + } else { + self.endOfData + } + ] + + iterator + ] + + result + } + + /** + * Converts this iterable of pairs into two lists of the first and second + * half of each pair. + * + *

The resulting lists are unmodifiable. + */ + def static Pair, List> unzip(Iterable> iterable) { + val size = iterable.size + val List a = Lists::newArrayListWithCapacity(size) + val List b = Lists::newArrayListWithCapacity(size) + + for (Pair pair : iterable) { + a.add(pair.key) + b.add(pair.value) + } + + a.unmodifiableView -> b.unmodifiableView + } + + /** + * Zips this iterable with its indices. + * + *

The resulting iterable's iterator does not support {@code remove()}. + */ + def static Iterable> zipWithIndex(Iterable iterable) { + iterable.zip(iterable.indices) + } + + /** + * Returns the minimum element of this iterable, according to the natural ordering + * of its elements. All elements in the iterable must implement the Comparable + * interface. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static > T min(Iterable iterable) { + iterable.min(Ordering::natural) + } + + /** + * Returns the minimum element of the given iterable, according to the order induced by + * the specified comparator. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static T min(Iterable iterable, Comparator comp) { + val i = iterable.iterator + var min = i.next + + while (i.hasNext) { + val next = i.next + if (comp.compare(next, min) < 0) { + min = next + } + } + + min + } + + /** + * Returns the minimum element of the given iterable based on the given {@code transformation}, + * according to the natural ordering of the values. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static > T minBy(Iterable iterable, (T) => U function) { + iterable.minBy(Ordering::natural, function) + } + + /** + * Returns the minimum element of the given iterable based on the given {@code transformation}, + * according to the order induced by the specified comparator. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static T minBy(Iterable iterable, Comparator comp, (T) => U function) { + val i = iterable.iterator + var min = i.next + + while (i.hasNext) { + val next = i.next + if (comp.compare(function.apply(next), function.apply(min)) < 0) { + min = next + } + } + + min + } + + /** + * Returns the maximum element of this iterable, according to the natural ordering + * of its elements. All elements in the iterable must implement the Comparable + * interface. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static > T max(Iterable iterable) { + iterable.max(Ordering::natural) + } + + /** + * Returns the maximum element of the given iterable, according to the order induced by + * the specified comparator. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static T max(Iterable iterable, Comparator comp) { + val i = iterable.iterator + var max = i.next + + while (i.hasNext) { + val next = i.next + if (comp.compare(next, max) > 0) { + max = next + } + } + + max + } + + /** + * Returns the maximum element of the given iterable based on the given {@code transformation}, + * according to the natural ordering of the values. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static > T maxBy(Iterable iterable, (T) => U function) { + iterable.maxBy(Ordering::natural, function) + } + + /** + * Returns the maximum element of the given iterable based on the given {@code transformation}, + * according to the order induced by the specified comparator. + * + * @throws NoSuchElementException if the iterable is empty. + */ + def static T maxBy(Iterable iterable, Comparator comp, (T) => U function) { + val i = iterable.iterator + var max = i.next + + while (i.hasNext) { + val next = i.next + if (comp.compare(function.apply(next), function.apply(max)) > 0) { + max = next + } + } + + max + } + + /** + * Partitions this iterable into a map of lists according to some discriminator function. + * + *

The resulting map and lists are unmodifiable. + */ + def static Map> groupBy(Iterable iterable, (T) => K function) { + val map = Maps::>newHashMap + + for (elem : iterable) { + val key = function.apply(elem) + map.getOrElseUpdate(key, Lists::newArrayList).add(elem) + } + + for (key : map.keySet) { + map.put(key, map.get(key).unmodifiableView) + } + + map.unmodifiableView + } + + /** + * Divides this iterable into unmodifiable sublists of the given size (the final list may be + * smaller). For example, grouping an iterable containing {@code [a, b, c, d, e]} with a group + * size of 3 yields {@code [[a, b, c], [d, e]]} -- an outer iterable containing two inner lists + * of three and two elements, all in the original order. + * + *

Iterators returned by the returned iterable do not support the {@link Iterator#remove()} method. + * The returned lists implement {@link RandomAccess}, whether or not the input list does. + * + * @return an iterable of unmodifiable lists containing the elements of {@code iterable} divided + * into groups + * @throws IllegalArgumentException if {@code size} is nonpositive + */ + def static Iterable> grouped(Iterable iterable, int size) { + Iterables::partition(iterable, size) + } +} diff --git a/src/main/java/com/github/xtension/MapExtensions.xtend b/src/main/java/com/github/xtension/MapExtensions.xtend new file mode 100644 index 0000000..58f9fed --- /dev/null +++ b/src/main/java/com/github/xtension/MapExtensions.xtend @@ -0,0 +1,33 @@ +package com.github.xtension + +import java.util.Map + +final class MapExtensions { + + private new() { + } + + /** + * Returns the value associated with a key, or a default value if the key is not contained in the map. + */ + def static V1 getOrElse(Map map, K key, V1 defaultValue) { + if (map.containsKey(key)) { + map.get(key) + } else { + defaultValue + } + } + + /** + * Returns the value associated with a key. If the given key is not contained in the map, + * stores a default value with the key in the map and returns that value. + */ + def static V getOrElseUpdate(Map map, K key, V defaultValue) { + if (map.containsKey(key)) { + map.get(key) + } else { + map.put(key, defaultValue) + defaultValue + } + } +} \ No newline at end of file diff --git a/src/main/java/com/github/xtension/ObjectExtensions.xtend b/src/main/java/com/github/xtension/ObjectExtensions.xtend new file mode 100644 index 0000000..8687349 --- /dev/null +++ b/src/main/java/com/github/xtension/ObjectExtensions.xtend @@ -0,0 +1,29 @@ +package com.github.xtension + +import com.google.common.annotations.Beta + +final class ObjectExtensions { + + private new() { + } + + /** + * Determines whether the value matches any value in the given list. + *

Example: + *

{@code 'a'.in('a', 'b', 'c')} (returns {@code true}) + */ + @Beta + def static boolean in(T obj, T other, T other2, T... others) { + obj == other || obj == other2 || others.contains(obj) + } + + /** + * Determines whether the value does not match any value in the given list. + *

Example: + *

{@code 'a'.notIn('c', 'd', 'e')} (returns {@code true}) + */ + @Beta + def static boolean notIn(T obj, T other, T other2, T... others) { + obj != other && obj != other2 && !others.contains(obj) + } +} \ No newline at end of file diff --git a/src/main/java/com/github/xtension/OptionalExtensions.xtend b/src/main/java/com/github/xtension/OptionalExtensions.xtend new file mode 100644 index 0000000..c46c145 --- /dev/null +++ b/src/main/java/com/github/xtension/OptionalExtensions.xtend @@ -0,0 +1,45 @@ +package com.github.xtension + +import com.google.common.base.Optional + +import static com.google.common.base.Preconditions.* + +final class OptionalExtensions { + + private new() { + } + + /** + * Returns an {@link Optional} containing the result of applying a function to the value of this + * {@code Optional} if it is nonempty. Otherwise returns an empty {@code Optional}. + */ + def static Optional map(Optional optional, (T) => U function) { + if (optional.present) { + Optional::of(function.apply(optional.get)) + } else { + Optional::absent + } + } + + /** + * Returns the result of applying a function to the value of this {@code Optional} if it is nonempty. + * Otherwise returns an empty {@code Optional}. Slightly different from {@link #map} in that the function + * is expected to return an {@code Optional}. + */ + def static Optional flatMap(Optional optional, (T) => Optional function) { + if (optional.present) { + checkNotNull(function.apply(optional.get)) + } else { + Optional::absent + } + } + + /** + * If this {@code Optional} is nonempty, invoke a procedure with the value, otherwise do nothing. + */ + def static void ifPresent(Optional optional, (T) => void procedure) { + if (optional.present) { + procedure.apply(optional.get) + } + } +}