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

scaloi.data.UnboundedBlockingFairKeyedQueue.scala Maven / Gradle / Ivy

/*
 * Copyright 2007 Learning Objects
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package scaloi
package data

import scaloi.syntax.any._

import scala.collection.mutable
import scala.concurrent.duration.FiniteDuration

/**
  * Somewhat fair queue of key value pairs. Values are released from the queue fairly
  * across the key space. For example, if key 'A' enqueues 3 items, key 'B' enqueues
  * 2 items and key 'C' enqueues one item, the resulting order will be 'ABCABA'.
  *
  * @tparam A the key type
  * @tparam B the value type
  */
final class UnboundedBlockingFairKeyedQueue[A, B] {
  /** Queue of key-value pairs to be run; only one element with a given key can be in this queue at a time. */
  private[this] val runQueue = mutable.Queue.empty[(A, B)]

  /** Backlog of values for keys already in the run queue. */
  private[this] val keyQueues = mutable.Map.empty[A, mutable.Queue[B]]

  /**
    * Offer a new key value pair to the queue.
    * @param key the key
    * @param value the value
    */
  def offer(key: A, value: B): Unit = synchronized {
    if (runQueue.exists(_._1 == key)) {
      // If a value for this key is already in the run queue, push this new value onto the key queue
      keyQueues.getOrElseUpdate(key, mutable.Queue.empty).enqueue(value)
    } else {
      // Otherwise just add his value to the run queue
      runQueue.enqueue(key -> value)
      notify()
    }
  }

  /**
    * Offer a new key value pair to the queue as a tuple.
    * @param tuple the tuple
    */
  def offer(tuple: (A, B)): Unit = offer(tuple._1, tuple._2)

  /**
    * Take the next value from this queue, blocking until one becomes available.
    * @return the next value
    */
  def take(): B = takeTuple()._2

  /**
    * Take the next value from this queue, blocking until one becomes available
    * or a specified amount of time has elapsed.
    * @param timeout the maximum amount of time to wait
    * @return the next value, or [[scala.None]] if a value could not be taken within the timeout.
    */
  def take(timeout: FiniteDuration): Option[B] = takeTuple(timeout) map (_._2)

  /**
    * Take the next tuple from this queue, blocking until one becomes available.
    * @return the next tuple
    */
  def takeTuple(): (A, B) = synchronized {
    // Grab the next key and value
    val (key, value) = next()
    shiftKeyQueue(key)
    key -> value
  }

  /**
    * Take the next tuple from this queue, blocking until one becomes available
    * or a specified amount of time has elapsed.
    * @param timeout the maximum amount of time to wait
    * @return the next tuple, or [[scala.None]] if a value could not be taken within the timeout.
    */
  def takeTuple(timeout: FiniteDuration): Option[(A, B)] = synchronized {
    next(timeout) tap {
      case Some((key, _)) => shiftKeyQueue(key)
      case None => ()
    }
  }

  /**
    * Clear this queue.
    */
  def clear(): Unit = synchronized {
    runQueue.clear()
    keyQueues.clear()
  }

  /**
    * Return a map of the values in this queue.
    * @return the values as a map
    */
  def toMap: Map[A, List[B]] = synchronized {
    runQueue.foldLeft(keyQueues.toMap.view.mapValues(_.toList).toMap) {
      case (map, (key, value)) => map + (key -> (value :: map.getOrElse(key, List.empty)))
    }
  }

  /**
    * Get the size of this queue.
    * @return the total number of elements in this queue
    */
  def size: Long = synchronized {
    runQueue.size.toLong + keyQueues.values.map(_.size).sum
  }

  /**
    * Test whether this is empty.
    * @return whether this is empty
    */
  def isEmpty: Boolean = synchronized {
    runQueue.isEmpty
  }

  /**
    * Test whether this is non empty.
    * @return whether this is non empty
    */
  def nonEmpty: Boolean = !isEmpty

  /* implementation details follow */

  /**
    * Wait for the run queue to be non-empty and then remove the first value.
    * @return the first value
    * @throws InterruptedException if the wait is interrupted
    */
  @throws[InterruptedException]
  private[this] def next(): (A, B) = {
    while (runQueue.isEmpty) {
      wait()
    }
    runQueue.dequeue()
  }

  /**
    * Wait for the run queue to be non-empty and then remove the first value.
    * @param timeout the maximum duration to wait for the run queue to be non-empty
    * @return the first value, or [[None]] if the queue did not become non-empty
    *         within the timeout
    * @throws InterruptedException if the wait is interrupted
    */
  @throws[InterruptedException]
  private[this] def next(timeout: FiniteDuration): Option[(A, B)] = {
    if (runQueue.isEmpty) {
      wait(timeout.toMillis)
    }

    if(runQueue.nonEmpty)
      Some(runQueue.dequeue())
    else
      None
  }

  /**
    * Move the next value from the queue for the provided key to the end of the
    * run queue, if one exists. Also removes the key queue if it becomes empty.
    * @param key the key for the queue to shift
    */
  private[this] def shiftKeyQueue(key: A): Unit = {
    keyQueues.get(key) foreach { keyQueue =>
      runQueue.enqueue(key -> keyQueue.dequeue())
      if (keyQueue.isEmpty)
        keyQueues.remove(key)
    }
  }
}

/** Queue companion. */
object UnboundedBlockingFairKeyedQueue {
  /**
    * Create an empty unbounded blocking fair keyed queue.
    * @tparam A the key type
    * @tparam B the value type
    * @return the queue
    */
  def empty[A, B]: UnboundedBlockingFairKeyedQueue[A, B] = new UnboundedBlockingFairKeyedQueue[A, B]
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy