All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jinq.orm.stream.JinqStream Maven / Gradle / Ivy

Go to download

Jinq public API for extending Java 8 streams with database functionality

There is a newer version: 2.0.2
Show newest version
package org.jinq.orm.stream;

import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

import org.jinq.tuples.Pair;
import org.jinq.tuples.Tuple3;
import org.jinq.tuples.Tuple4;
import org.jinq.tuples.Tuple5;
import org.jinq.tuples.Tuple6;
import org.jinq.tuples.Tuple7;
import org.jinq.tuples.Tuple8;

/**
 * The JinqStream is a normal Java 8 stream extended with extra methods. These
 * extra methods are needed because:
 * 
    *
  • They can be more easily analyzed by Jinq *
  • They provide extra functionality that more closely mirrors database query * functionality *
  • They have names that are derived from database query syntax instead of * names derived from functional programming syntax *
*

* Programmers typically get access to JinqStreams through a * JinqStream provider. For example, the * {@code org.jinq.jpa.JinqJPAStreamProvider} is able to create streams of JPA * entities from a database. It is also possible to create * JinqStreams from collections and single objects. * * @param * type of object that is being processed by the stream */ public interface JinqStream extends Stream { @FunctionalInterface public static interface Where extends Serializable { public boolean where(U obj) throws E; } @FunctionalInterface public static interface WhereWithSource extends Serializable { public boolean where(U obj, InQueryStreamSource source) throws E; } /** * Filters the elements of the stream. * *

    * {@code JinqStream stream = ...;
    * JinqStream result = stream.where(c -> c.getName().equals("Alice"));
    * }
    * 
* * @param test * function applied to the elements of the stream. When passed an * element from the stream, the function should return true if the * element should be kept. if the function returns false, the * element is discarded. * @return a new stream that returns only the elements satisfying the filter */ public JinqStream where(Where test); /** * Filters the elements of the stream. This version allows the filter * function to take a second parameter with an InQueryStreamSource. This lets * the function create new streams of elements that it can use in subqueries. * * @see #where(Where) * @param test * function applied to each element of the stream. The function is * passed an element from the stream as well as an * {@link InQueryStreamSource}. The function should return true if * the element should be kept. if the function returns false, the * element is discarded. * @return a new stream that returns only the elements satisfying the filter */ public JinqStream where(WhereWithSource test); @FunctionalInterface public static interface Select extends Serializable { public V select(U val); } @FunctionalInterface public static interface SelectWithSource extends Serializable { public V select(U val, InQueryStreamSource source); } /** * Transforms the elements in the stream. The method allows you to rewrite * each element from the stream, so that they contain only certain fields or * to do some calculation based on the values of the fields. * *
    * {@code JinqStream stream = ...;
    * JinqStream result = stream.select(c -> c.getName());
    * }
    * 
* * @param select * function applied to the elements of the stream. When passed an * element from the stream, the function should return a new value * that should be used instead of the element in the stream. * @return a new stream that uses only the new rewritten stream elements */ public JinqStream select(Select select); /** * Transforms the elements in the stream. This version also passes an * {@link InQueryStreamSource} to the select function so that the function * can create new streams of elements to use in subqueries. * * @see #select(Select) */ public JinqStream select(SelectWithSource select); /** * Transforms the elements in the stream. The method allows you to rewrite * each element from the stream, so that they contain only certain fields or * to do some calculation based on the values of the fields. Unlike a normal * select(), this method allows you to return a stream of elements. The * stream elements will all be added to the final stream. * *
    * {@code JinqStream stream = ...;
    * JinqStream result = stream.selectAll(c -> JinqStream.from(c.getCities()));
    * }
    * 
* * @see #select(Select) * @param select * function applied to the elements of the stream. When passed an * element from the stream, the function should return a stream of * new values that will be flattened and placed in the new stream * @return a new stream that uses only the new rewritten stream elements */ public JinqStream selectAll(Join select); /** * Transforms the elements in the stream. This version also passes an * {@link InQueryStreamSource} to the select function so that the function * can create new streams of elements to use in subqueries. * * @see #selectAll(Join) */ public JinqStream selectAll(JoinWithSource select); /** * A variant of selectAll() that can be used if you want to join to a * collection without the trouble of converting it to a JinqStream * first. * *
    * {@code JinqStream stream = ...;
    * JinqStream result = stream.selectAll(c -> c.getCities());
    * }
    * 
* * @see #selectAll(Join) * @param select * function applied to the elements of the stream. When passed an * element from the stream, the function should return a collection * of new values that will be flattened and placed in the new stream * @return a new stream that uses only the new rewritten stream elements */ public JinqStream selectAllList(JoinToIterable select); // TODO: Joins are somewhat dangerous because certain types of joins that are // expressible here are NOT expressible in SQL. (Moving a join into // a from clause is only possible if the join does not access variables from // other things in the FROM clause *if* it ends up as a subquery. If we can // express it as not a subquery, then it's ok. // TODO: Perhaps only providing a join(DBSet other) is safer because // I think it will translate into valid SQL code, but it prevents people from // using navigational queries e.g. customers.join(customer -> // customer.getPurchases); @FunctionalInterface public static interface Join extends Serializable { public JinqStream join(U val); } @FunctionalInterface public static interface JoinWithSource extends Serializable { public JinqStream join(U val, InQueryStreamSource source); } @FunctionalInterface public static interface JoinToIterable extends Serializable { public Iterable join(U val); } /** * Pairs up each entry of the stream with a stream of related elements. * *
    * {@code JinqStream stream = ...;
    * JinqStream> result = 
    *    stream.join(c -> JinqStream.from(c.getCities()));
    * }
    * 
* * @param join * function applied to the elements of the stream. When passed an * element from the stream, the function should return a stream of * values that should be paired up with that stream element. * @return a new stream with the paired up elements */ public JinqStream> join(Join join); /** * Pairs up each entry of the stream with a stream of related elements. This * version also passes an {@link InQueryStreamSource} to the join function so * that the function can join elements with unrelated streams of entities * from a database. * * @see #join(Join) */ public JinqStream> join(JoinWithSource join); /** * A variant of join() that can be used if you want to join to a * collection without the trouble of converting it to a JinqStream * first. * * @see #join(Join) */ public JinqStream> joinList(JoinToIterable join); /** * Pairs up each entry of the stream with a stream of related elements. Uses * a left outer join during the pairing up process, so even if an element is * not joined with anything, a pair will still be created in the output * stream consisting of the element paired with null. * *
    * {@code JinqStream stream = ...;
    * JinqStream> result = 
    *    stream.leftOuterJoin(c -> JinqStream.from(c.getMountain()));
    * JinqStream> result = 
    *    stream.leftOuterJoin(c -> JinqStream.of(c.getHighestMountain()));
    * }
    * 
* * @see #join(Join) * @param join * function applied to the elements of the stream. When passed an * element from the stream, the function should return a stream of * values that should be paired up with that stream element. The * function must use a JPA association or navigational link as the * base for the stream returned. Both singular or plural * associations are allowed. * @return a new stream with the paired up elements */ public JinqStream> leftOuterJoin(Join join); /** * A variant of leftOuterJoin() that can be used if you want to join to a * collection without the trouble of converting it to a JinqStream * first. * * @see #leftOuterJoin(Join) */ public JinqStream> leftOuterJoinList(JoinToIterable join); @FunctionalInterface public static interface WhereForOn extends Serializable { public boolean where(U obj1, V obj2); } /** * Pairs up each entry of the stream with a stream of related elements. Uses * a left outer join during the pairing up process, so even if an element is * not joined with anything, a pair will still be created in the output * stream consisting of the element paired with null. This version also passes * an {@link InQueryStreamSource} to the join function so that the function * can join elements with unrelated streams of entities from a database * and an ON clause can be specified that will determine which elements from * the two streams will be joined together. * *
    * {@code JinqStream stream = ...;
    * JinqStream> result = 
    *    stream.leftOuterJoin(
    *            (c, source) -> source.stream(Mountain.class), 
    *            (country, mountain) -> country.getName().equals(mountain.getCountry()));
    * }
    * 
* * @see #leftOuterJoin(Join) * @param join * function applied to the elements of the stream. When passed an * element from the stream, the function should return a stream of * values that should be paired up with that stream element. The * function must use a JPA association or navigational link as the * base for the stream returned. Both singular or plural * associations are allowed. * @param on * this is a comparison function that returns true if the elements * from the two streams should be joined together. It is similar to * a standard where() clause except that the elements from the two * streams are passed in as separate parameters for convenience * (as opposed to being passed in as a pair) * @return a new stream with the paired up elements */ public JinqStream> leftOuterJoin(JoinWithSource join, WhereForOn on); /** * Performs a full cross-join of the elements of two streams. * * @param join other stream to perform the cross join with * @return a new stream where each element of the stream is paired up with * each element of the join stream */ public JinqStream> crossJoin(JinqStream join); @FunctionalInterface public static interface AggregateGroup extends Serializable { public V aggregateSelect(W key, JinqStream val); } /** * Groups together elements from the stream that share a common key. * Aggregates can then be calculated over the elements in each group. * *
    * {@code JinqStream stream = ...;
    * JinqStream> result = 
    *    stream.group(c -> c.getCountry(), (key, cities) -> cities.count());
    * }
    * 
* * @param select * function applied to each element of the stream that returns the * key to be used to group elements together * @param aggregate * function applied to each group and calculates an aggregate value * over the group. The function is passed the key for the group and * a JinqStream of elements contained inside that group. It should * return the aggregate value calculated for that group. * @return a new stream containing a tuple for each group. The tuple contains * the key for the group and any calculated aggregate values. */ public JinqStream> group(Select select, AggregateGroup aggregate); /** * Calculates two aggregate values instead of one aggregate value for grouped * stream elements. * * @see #group(Select, AggregateGroup) */ public JinqStream> group(Select select, AggregateGroup aggregate1, AggregateGroup aggregate2); /** * Calculates three aggregate values instead of one aggregate value for * grouped stream elements. * * @see #group(Select, AggregateGroup) */ public JinqStream> group( Select select, AggregateGroup aggregate1, AggregateGroup aggregate2, AggregateGroup aggregate3); /** * Calculates four aggregate values instead of one aggregate value for * grouped stream elements. * * @see #group(Select, AggregateGroup) */ public JinqStream> group( Select select, AggregateGroup aggregate1, AggregateGroup aggregate2, AggregateGroup aggregate3, AggregateGroup aggregate4); /** * Calculates five aggregate values instead of one aggregate value for * grouped stream elements. * * @see #group(Select, AggregateGroup) */ public JinqStream> group( Select select, AggregateGroup aggregate1, AggregateGroup aggregate2, AggregateGroup aggregate3, AggregateGroup aggregate4, AggregateGroup aggregate5); /** * Calculates six aggregate values instead of one aggregate value for * grouped stream elements. * * @see #group(Select, AggregateGroup) */ public JinqStream> group( Select select, AggregateGroup aggregate1, AggregateGroup aggregate2, AggregateGroup aggregate3, AggregateGroup aggregate4, AggregateGroup aggregate5, AggregateGroup aggregate6); /** * Calculates seven aggregate values instead of one aggregate value for * grouped stream elements. * * @see #group(Select, AggregateGroup) */ public JinqStream> group( Select select, AggregateGroup aggregate1, AggregateGroup aggregate2, AggregateGroup aggregate3, AggregateGroup aggregate4, AggregateGroup aggregate5, AggregateGroup aggregate6, AggregateGroup aggregate7); // TODO: This interface is a little iffy since the function can potentially // return different number types // and things can't be checked until runtime, but Java type inferencing // currently can't // disambiguate between different methods that take functions with different // return types. // In most cases, this should be fine as long as programmers define V as // something specific // like Integer or Double instead of something generic like Number. // These interfaces are used to define the lambdas used as parameters to // various aggregation // operations. @FunctionalInterface public static interface CollectNumber> extends Serializable { public V aggregate(U val); } @FunctionalInterface public static interface CollectComparable> extends Serializable { public V aggregate(U val); } @FunctionalInterface public static interface CollectInteger extends CollectNumber { } @FunctionalInterface public static interface CollectLong extends CollectNumber { } @FunctionalInterface public static interface CollectDouble extends CollectNumber { } @FunctionalInterface public static interface CollectBigDecimal extends CollectNumber { } @FunctionalInterface public static interface CollectBigInteger extends CollectNumber { } // Having separate sum() methods for different types is messy but due to // problems with Java's type inferencing and the fact that JPQL uses // different return types for a sum than the types being summed over, // this is the only way to do sum operations in a type-safe way. /** * Calculates a sum over the elements of a stream. Different sum methods are * provided for calculating the sum of integer, long, double, BigDecimal, and * BigInteger values. * *
    * {@code JinqStream stream = ...;
    * long totalPopulation = stream.sumInteger(c -> c.getPopulation()); 
    * }
    * 
* * @param aggregate * function applied to each element of the stream. When passed an * element of the stream, it should return the value that should be * added to the sum. * @return the sum of the values returned by the function */ public Long sumInteger(CollectInteger aggregate); /** @see #sumInteger(CollectInteger) */ public Long sumLong(CollectLong aggregate); /** @see #sumInteger(CollectInteger) */ public Double sumDouble(CollectDouble aggregate); /** @see #sumInteger(CollectInteger) */ public BigDecimal sumBigDecimal(CollectBigDecimal aggregate); /** @see #sumInteger(CollectInteger) */ public BigInteger sumBigInteger(CollectBigInteger aggregate); // TODO: It's more type-safe to have separate maxDouble(), maxDate(), etc. // methods, // but it's too messy, so I'll provide this simpler max() method for now /** * Finds the largest or maximum element of a stream. * *
    * {@code JinqStream stream = ...;
    * Date birthdayOfYoungest = stream.max(s -> s.getBirthday()); 
    * }
    * 
* * @param aggregate * function applied to each element of the stream. When passed an * element of the stream, it should return the value that should be * compared. * @return the maximum of the values returned by the function */ public > V max(CollectComparable aggregate); /** * Finds the smallest or minimum element of a stream. * *
    * {@code JinqStream stream = ...;
    * Date birthdayOfOldest = stream.min(s -> s.getBirthday()); 
    * }
    * 
* * @see #max(CollectComparable) * @param aggregate * function applied to each element of the stream. When passed an * element of the stream, it should return the value that should be * compared. * @return the minimum of the values returned by the function */ public > V min(CollectComparable aggregate); /** * Finds the average of the elements of a stream. * *
    * {@code JinqStream stream = ...;
    * double averageAge = stream.avg(s -> s.getage()); 
    * }
    * 
* * @param aggregate * function applied to each element of the stream. When passed an * element of the stream, it should return the value that should be * included in the average * @return the average of the values returned by the function */ public > Double avg( CollectNumber aggregate); @FunctionalInterface public static interface AggregateSelect extends Serializable { public V aggregateSelect(JinqStream val); } /** * Calculates more than one aggregate function over the elements of the * stream. * *
    * {@code JinqStream stream = ...;
    * Pair result = stream.aggregate(
    *    c -> c.sumInteger(c.getPopulation()),
    *    c -> c.count()); 
    * }
    * 
* * @param aggregate1 * a function that takes a stream and returns the first calculated * aggregate value for the stream * @param aggregate2 * a function that takes a stream and returns a second calculated * aggregate value for the stream * @return a tuple of the calculated aggregate values */ public Pair aggregate(AggregateSelect aggregate1, AggregateSelect aggregate2); /** * @see #aggregate(AggregateSelect, AggregateSelect) */ public Tuple3 aggregate(AggregateSelect aggregate1, AggregateSelect aggregate2, AggregateSelect aggregate3); /** * @see #aggregate(AggregateSelect, AggregateSelect) */ public Tuple4 aggregate( AggregateSelect aggregate1, AggregateSelect aggregate2, AggregateSelect aggregate3, AggregateSelect aggregate4); /** * @see #aggregate(AggregateSelect, AggregateSelect) */ public Tuple5 aggregate( AggregateSelect aggregate1, AggregateSelect aggregate2, AggregateSelect aggregate3, AggregateSelect aggregate4, AggregateSelect aggregate5); /** * Sorts the elements of a stream in ascending order based on the value * returned. The sort is stable, so it is possible to sort the stream * multiple times in order to have multiple sort keys. The last sort becomes * the primary sort key, and earlier sorts become lesser keys. * * @param sortField * function applied to each element of the stream. When passed an * element of the stream, it should return the value that should be * used as the sorting value of the element * @return sorted stream */ public > JinqStream sortedBy( CollectComparable sortField); /** * Sorts the elements of a stream in descending order based on the value * returned. * * @see #sortedBy(CollectComparable) * @param sortField * function applied to each element of the stream. When passed an * element of the stream, it should return the value that should be * used as the sorting value of the element * @return sorted stream */ public > JinqStream sortedDescendingBy( CollectComparable sortField); // Overriding the Stream API versions to return a JinqStream instead, so it's // easier to chain them @Override public JinqStream skip(long n); @Override public JinqStream limit(long n); @Override public JinqStream distinct(); /** * Counts the elements in the stream. If the stream contains only a single * field of data (i.e. not a tuple) as derived from a database query, then * the count will be of non-NULL elements only. If the stream contains more * than one field of data (i.e. a tuple) or if the stream is streaming * in-memory data, then the count will include NULL values. * * @see Stream#count() */ @Override public long count(); /** * A convenience method for getting the contents of a stream when it contains * 0 or 1 values. If the stream contains more than one value, an exception * will be thrown. * * It cannot be used in subqueries. * * @see Stream#findFirst() * * @return an Optional with the single element contained in the stream or * an empty Optional if the stream is empty * @throws java.util.NoSuchElementException * stream contains more than one element */ public Optional findOne(); /** * If the stream contains only a single value, this method will return that * value. This method is convenient for getting the results of queries that * contain only a single value. It is also useful in subqueries. * * @return the single element contained in the stream * @throws java.util.NoSuchElementException * stream contains zero or more than one element */ public T getOnlyValue(); /** * Convenience method that collects the stream contents into a List. * * @return a list of all the elements from the stream */ // TODO: Should toList() throw an exception? public List toList(); /** * Returns the query that Jinq will send to the database to generate the * values of the stream. * * @return the database query string or null if Jinq cannot find * a database query equivalent to the contents of the stream. */ public String getDebugQueryString(); /** * Used for recording an exception that occurred during processing somewhere * in the stream chain. * * @param source * lambda object that caused the exception (used so that if the * same lambda causes multiple exceptions, only some of them need * to be recorded in order to avoid memory issues) * @param exception * actual exception object */ @Deprecated public void propagateException(Object source, Throwable exception); @Deprecated public Collection getExceptions(); /** * Sets a hint on the stream for how the query should be executed * * @param name * name of the hint to change * @param value * value to assign to the hint * @return a pointer to the stream, to make it easier to chain method calls * on the stream */ public JinqStream setHint(String name, Object value); /** * Easy way to get a JinqStream from a collection. */ public static JinqStream from(Collection collection) { return new NonQueryJinqStream<>(collection.stream()); } /** * Creates a JinqStream containing a single object. */ public static JinqStream of(U value) { return new NonQueryJinqStream<>(Stream.of(value)); } }