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

org.jinq.orm.stream.NonQueryJinqStream 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.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.jinq.tuples.Pair;
import org.jinq.tuples.Tuple3;

import ch.epfl.labos.iu.orm.DateSorter;
import ch.epfl.labos.iu.orm.DoubleSorter;
import ch.epfl.labos.iu.orm.IntSorter;
import ch.epfl.labos.iu.orm.StringSorter;

public class NonQueryJinqStream extends LazyWrappedStream implements JinqStream
{
   public NonQueryJinqStream(Stream wrapped)
   {
      super(wrapped);
   }

   NonQueryJinqStream()
   {
      super();
   }
   
   protected  Stream wrap(Stream toWrap)
   {
      return new NonQueryJinqStream<>(toWrap);
   }

   
   @Override
   public  JinqStream where(Where test)
   {
      return new NonQueryJinqStream<>(filter(val -> { 
            try { 
               return test.where(val); 
            } catch (Exception e) {
               // Record that an exception occurred
               propagateException(test, e);
               // Throw a runtime exception to try and kill the stream?
               throw new RuntimeException(e);
            }} ));
   }

   @Override
   public  JinqStream select(Select select)
   {
      return new NonQueryJinqStream<>(map( val -> select.select(val) ));
   }

   @Override
   public  JinqStream> join(Join join)
   {
      // TODO: This stream should be constructed on the fly
      final Stream.Builder> streamBuilder = Stream.builder();
      forEach( left -> {
         join.join(left).forEach( right -> 
            { streamBuilder.accept(new Pair<>(left, right)); });
         });
      return new NonQueryJinqStream<>(streamBuilder.build());
   }
   
   @Override
   public JinqStream unique()
   {
      return new NonQueryJinqStream<>(distinct());
   }
   
   @Override
   public  JinqStream> group(Select select, AggregateGroup aggregate)
   {
      // TODO: This stream should be constructed on the fly
      final Stream.Builder> streamBuilder = Stream.builder();
      
      // TODO: Rewrite to use Collectors.groupingBy()
      HashMap> map = new HashMap<>();
      forEach( val -> {
         U group = select.select(val);
         if (!map.containsKey(group))
            map.put(group, new ArrayList<>());
         map.get(group).add(val);
      });
      for (Map.Entry> entry: map.entrySet())
         streamBuilder.accept(new Pair<>(entry.getKey(), aggregate.aggregateSelect(entry.getKey(), new NonQueryJinqStream<>(entry.getValue().stream()))));
      return new NonQueryJinqStream<>(streamBuilder.build());
   }

   @Override
   public double sumDouble(AggregateDouble aggregate)
   {
      // TODO: Rewrite using a summing collector 
      return reduce(0.0, 
            (accum, val) -> accum + aggregate.aggregate(val),
            (accum1, accum2) -> accum1 + accum2);
   }
   
   @Override
   public int sumInt(AggregateInteger aggregate)
   {
      return reduce(0, 
            (accum, val) -> accum + aggregate.aggregate(val),
            (accum1, accum2) -> accum1 + accum2);
   }

   @Override
   public double maxDouble(AggregateDouble aggregate)
   {
      return reduce(Double.NEGATIVE_INFINITY, 
            (accum, val) -> Math.max(accum, aggregate.aggregate(val)),
            (accum1, accum2) -> Math.max(accum1, accum2));
   }
   
   @Override
   public int maxInt(AggregateInteger aggregate)
   {
      return reduce(Integer.MIN_VALUE, 
            (accum, val) -> Math.max(accum, aggregate.aggregate(val)),
            (accum1, accum2) -> Math.max(accum1, accum2));
   }
   
   @Override
   public  U selectAggregates(AggregateSelect aggregate)
   {
      return aggregate.aggregateSelect(this);
   }

   @Override
   public JinqStream sortedByIntAscending(IntSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
         (o1, o2) -> sorter.value(o1) - sorter.value(o2)));
   }

   @Override
   public JinqStream sortedByIntDescending(IntSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> sorter.value(o2) - sorter.value(o1)));
   }

   @Override
   public JinqStream sortedByDoubleAscending(DoubleSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> (int)Math.signum(sorter.value(o1) - sorter.value(o2))));
   }

   @Override
   public JinqStream sortedByDoubleDescending(DoubleSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> (int)Math.signum(sorter.value(o2) - sorter.value(o1))));
   }

   @Override
   public JinqStream sortedByStringAscending(StringSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> sorter.value(o1).compareTo(sorter.value(o2))));
   }

   @Override
   public JinqStream sortedByStringDescending(StringSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> -sorter.value(o1).compareTo(sorter.value(o2))));
   }

   @Override
   public JinqStream sortedByDateAscending(DateSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> sorter.value(o1).compareTo(sorter.value(o2))));
   }

   @Override
   public JinqStream sortedByDateDescending(DateSorter sorter)
   {
      return new NonQueryJinqStream<>(sorted(
            (o1, o2) -> -sorter.value(o1).compareTo(sorter.value(o2))));
   }

   @Override
   public JinqStream firstN(int n)
   {
      return new NonQueryJinqStream<>(limit(n));
   }
   
   @Override
   public T getOnlyValue()
   {
      List vals = collect(Collectors.toList());
      if (vals.size() == 1) return vals.get(0);
      throw new NoSuchElementException();
   }
   
   @Override
   public JinqStream with(T toAdd)
   {
      return new NonQueryJinqStream<>(
            Stream.concat(this, Stream.of(toAdd)));
   }
   
   @Override
   public List toList()
   {
      return collect(Collectors.toList());
   }
   
   @Override
   public String getDebugQueryString()
   {
      // TODO: It would be nice if this could follow the stream chain
      //    down to get the underlying query (the stream chain isn't currently
      //    recorded, so this is not possible at the moment).
      return null;
   }
   
   protected Map recordedExceptions = new HashMap<>();
   
   @Override
   public void propagateException(Object source, Throwable exception)
   {
      if (!recordedExceptions.containsKey(source))
         recordedExceptions.put(source, exception);
   }

   @Override
   public Collection getExceptions()
   {
      return recordedExceptions.values();
   }

   @Override
   public  Pair aggregate(AggregateSelect aggregate1, AggregateSelect aggregate2)
   {
      AggregateSelect[] aggregates = new AggregateSelect[]
            {
               aggregate1, aggregate2
            };
      Object [] results = multiaggregate(aggregates);
      return new Pair<>((U)results[0], (V)results[1]);
   }

   @Override
   public  Tuple3 aggregate(AggregateSelect aggregate1,
         AggregateSelect aggregate2, AggregateSelect aggregate3)
   {
      AggregateSelect[] aggregates = new AggregateSelect[]
            {
               aggregate1, aggregate2, aggregate3
            };
      Object [] results = multiaggregate(aggregates);
      return new Tuple3<>((U)results[0], (V)results[1], (W)results[2]);
   }

   Object[] multiaggregate(AggregateSelect[] aggregates)
   {
      final int MAX_QUEUE_SIZE = 100;
      final Object DONE = new Object();
      
      // Make a copy of the input stream for each aggregate being calculated.
      final JinqStream[] inputStreams = new JinqStream[aggregates.length];
      final ArrayBlockingQueue[] inputQueues = new ArrayBlockingQueue[aggregates.length];
      for (int n = 0; n < aggregates.length; n++)
      {
         inputQueues[n] = new ArrayBlockingQueue<>(MAX_QUEUE_SIZE);
      }
      
      Runnable startIterator = new Runnable() {
         boolean isStarted = false;
         @Override public synchronized void run()
         {
            if (isStarted) return;
            isStarted = true;
            new Thread() {
               @Override public void run()
               {
                  forEach( val -> {
                     for (int n = 0; n < inputQueues.length; n++)
                     {
                        try {
                           inputQueues[n].put(val);
                        } catch (InterruptedException e)
                        {
                           Thread.currentThread().interrupt();
                        }
                     }
                  });
                  try {
                     for (int n = 0; n < inputQueues.length; n++)
                        inputQueues[n].put(DONE);
                  } catch (InterruptedException e)
                  {
                     Thread.currentThread().interrupt();
                  }
               }
            }.start();
         }};
      
      // Run each aggregator in a separate thread so that we can
      // use producer-consumer queues and hence avoid using too much
      // memory.
      Thread [] aggregateThreads = new Thread[aggregates.length];
      final Object [] results = new Object[aggregates.length];
      for (int n = 0; n < aggregates.length; n++)
      {
         final int idx = n;
         final AggregateSelect fn = aggregates[idx];
         aggregateThreads[n] = new Thread() {
            @Override public void run()
            {
               startIterator.run();
               Iterator inputIterator = new NextOnlyIterator()
                     {
                        @Override
                        protected void generateNext()
                        {
                           Object taken = DONE;
                           try {
                              taken = inputQueues[idx].take();
                           } catch (InterruptedException e)
                           {
                              Thread.currentThread().interrupt();
                           }
                           if (taken == DONE)
                              noMoreElements();
                           else
                              nextElement((T)taken);
                        }
                     };
               JinqStream stream = new NonQueryJinqStream<>(
                     StreamSupport.stream(
                           Spliterators.spliteratorUnknownSize(
                                 inputIterator, 
                                 Spliterator.CONCURRENT), 
                           false));
               results[idx] = fn.aggregateSelect(stream);
            }
         };
         aggregateThreads[n].start();
      }
      for (int n = 0; n < aggregateThreads.length; n++)
      {
         try {
            aggregateThreads[n].join();
         } catch (InterruptedException e)
         {
            Thread.currentThread().interrupt();
         }
      }
      return results;
   }

   @Override
   public JinqStream setHint(String name, Object value)
   {
      return this;
   }

}