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

com.lithium.flow.util.Batcher Maven / Gradle / Ivy

/*
 * Copyright 2016 Lithium Technologies, Inc.
 *
 * 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 com.lithium.flow.util;

import static com.google.common.base.Preconditions.checkNotNull;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.google.common.collect.Lists;

/**
 * @author Pradeep Gollakota
 */
public class Batcher {

	private final Batch POISON_PILL;
	private final int batchSize;
	private final BlockingQueue> batchQueue;
	private Batch currentBatch;
	private boolean finished;

	public Batcher(int batchSize) {
		this(batchSize, Integer.MAX_VALUE);
	}

	public Batcher(int batchSize, int maxNumBatches) {
		this.batchSize = batchSize;
		this.currentBatch = new Batch<>(batchSize);
		this.batchQueue = new LinkedBlockingQueue<>(maxNumBatches);
		this.finished = false;
		this.POISON_PILL = new Batch<>(0);
	}

	public synchronized void offer(@Nonnull T item) {
		checkNotNull(item);

		if (finished) {
			throw new IllegalStateException("Cannot offer more elements after close() has been called");
		}

		currentBatch.add(item);
		if (currentBatch.size() >= batchSize) {
			batchQueue.offer(currentBatch);
			currentBatch = new Batch<>(batchSize);
		}
	}

	@Nullable
	public Batch take() throws InterruptedException {
		Batch toReturn = batchQueue.take();
		if (toReturn == POISON_PILL) {
			batchQueue.offer(POISON_PILL);
			return null;
		}
		return toReturn;
	}

	@Nullable
	public Batch poll(long timeout, TimeUnit unit) throws InterruptedException {
		Batch toReturn = batchQueue.poll(timeout, unit);
		if (toReturn == POISON_PILL) {
			batchQueue.offer(POISON_PILL);
			return null;
		} else if (toReturn == null) {
			// lock the batcher so we can drain a partial batch
			synchronized (this) {
				if (currentBatch.size() == 0) {
					// return a static empty batch
					// it's ok to return the poison pill since batches are immutable for consumers
					toReturn = POISON_PILL;
				} else {
					toReturn = currentBatch;
					currentBatch = new Batch<>(batchSize);
				}
			}
		}
		return toReturn;
	}

	/**
	 * @deprecated Use {@link #close()} instead.
	 */
	@Deprecated
	public synchronized void finish() {
		close();
	}

	public synchronized void close() {
		if (finished) {
			throw new IllegalStateException("Batcher already finished");
		}
		finished = true;
		batchQueue.offer(currentBatch); // Offer any remaining items as a partial batch
		batchQueue.offer(POISON_PILL); // Kill the consumers
		currentBatch = null;
	}

	public static final class Batch implements Iterable {

		private final ArrayList itemQueue;

		private Batch(int size) {
			this.itemQueue = Lists.newArrayListWithCapacity(size);
		}

		private void add(T item) {
			itemQueue.add(item);
		}

		public int size() {
			return itemQueue.size();
		}

		@Override
		public Iterator iterator() {
			return new Iterator() {
				private final Iterator internal = itemQueue.iterator();

				@Override
				public boolean hasNext() {
					return internal.hasNext();
				}

				@Override
				public T next() {
					return internal.next();
				}
			};
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy