/* ********************************************************************** **
** Copyright notice **
** **
** (c) 2005-2009 RSSOwl Development Team **
** http://www.rssowl.org/ **
** **
** All rights reserved **
** **
** This program and the accompanying materials are made available under **
** the terms of the Eclipse Public License v1.0 which accompanies this **
** distribution, and is available at: **
** http://www.rssowl.org/legal/epl-v10.html **
** **
** A copy is found in the file epl-v10.html and important notices to the **
** license from the team is found in the textfile LICENSE.txt distributed **
** in this package. **
** **
** This copyright notice MUST APPEAR in all copies of the file! **
** **
** Contributors: **
** RSSOwl Development Team - initial API and implementation **
** **
** ********************************************************************** */
package org.rssowl.core.util;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* The <code>BatchedBuffer</code> can be used to batch certain types of Objects
* for later processing. The type of Object is identified by the generic
* <code>T</code>.
*
* @author bpasero
* @param <T> The type that will be buffered
*/
public class BatchedBuffer<T> {
private final int fBatchInterval;
private final List<T> fBuffer;
private final Job fBufferProcessor;
private final Receiver<T> fReceiver;
private final AtomicBoolean fSealed = new AtomicBoolean(false);
private final AtomicBoolean fRunning = new AtomicBoolean(false);
/**
* @param <T> The type that will be received from the
* <code>BatchedBuffer</code>
*/
public interface Receiver<T> {
/**
* Asks the implementor to process the given Collection of Objects. This
* method will be called after <code>batchInterval</code> millies.
*
* @param objects A Collection of Objects that have been added during
* <code>batchInterval</code>.
* @param job the {@link Job} that is calling the receive method.
* @param monitor a progress monitor to report progress and react on
* cancellation.
* @return the {@link IStatus} of the operation.
*/
IStatus receive(Collection<T> objects, Job job, IProgressMonitor monitor);
}
/**
* @param receiver An instance of <code>Receiver</code> to process the
* elements of this buffer after <code>batchInterval</code> millies.
* @param batchInterval The interval in millies before types get processed.
*/
public BatchedBuffer(Receiver<T> receiver, int batchInterval) {
this(receiver, batchInterval, null);
}
/**
* @param receiver An instance of <code>Receiver</code> to process the
* elements of this buffer after <code>batchInterval</code> millies.
* @param batchInterval The interval in millies before types get processed.
* @param taskName if provided, the batched buffer will show progress with the
* given name.
*/
public BatchedBuffer(Receiver<T> receiver, int batchInterval, String taskName) {
fReceiver = receiver;
fBatchInterval = batchInterval;
fBuffer = new ArrayList<T>();
fBufferProcessor = createBufferProcessor(taskName);
}
/**
* Adds all Objects of the given Collection to this Buffer. The Buffer will be
* cleared after <code>batchInterval</code> millies by calling the
* <code>receive(Set)</code> method.
*
* @param objects The Collection of Objects to add into this Buffer.
*/
public void addAll(Collection<T> objects) {
if (objects.isEmpty() || fSealed.get())
return;
synchronized (fBuffer) {
/* Check again for being sealed */
if (fSealed.get())
return;
/* New Batch */
if (fBuffer.isEmpty()) {
fBuffer.addAll(objects);
fBufferProcessor.schedule(fBatchInterval);
}
/* Existing Batch */
else {
for (T object : objects) {
if (!fBuffer.contains(object))
fBuffer.add(object);
}
}
}
}
/**
* Cancels the BatchedBuffer from running or processing tasks.
*
* @param joinRunning If <code>TRUE</code>, join the running Jobs that are not
* yet done.
*/
public void cancel(boolean joinRunning) {
/* Seal the Buffer */
fSealed.set(true);
/* Wait until Buffer has completed work */
if (joinRunning) {
/* Do not wait until buffer is scheduled, schedule right now instead */
if (isScheduled() && !isRunning()) {
fBufferProcessor.cancel();
fBufferProcessor.schedule();
}
/* Wait until Buffer is done processing */
while (isScheduled()) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
break;
}
}
}
/* Cancel Batched Buffer and don't wait for finish */
else
fBufferProcessor.cancel();
}
/**
* @return <code>true</code> if this buffer is running or scheduled and
* <code>false</code> otherwise.
*/
public boolean isScheduled() {
return isRunning() || Job.getJobManager().find(this).length != 0;
}
/**
* @return <code>true</code> if this buffer is running and <code>false</code>
* otherwise.
*/
public boolean isRunning() {
return fRunning.get();
}
/* Creates a Job to process the contents of the Buffer */
private Job createBufferProcessor(String taskName) {
Job job = new Job(taskName != null ? taskName : "") { //$NON-NLS-1$
@Override
protected IStatus run(IProgressMonitor monitor) {
fRunning.set(true);
try {
if (monitor.isCanceled())
return Status.CANCEL_STATUS;
IStatus status;
synchronized (fBuffer) {
status = fReceiver.receive(new ArrayList<T>(fBuffer), this, monitor);
fBuffer.clear();
}
return status;
} finally {
fRunning.set(false);
}
}
@Override
public boolean belongsTo(Object family) {
return family == BatchedBuffer.this;
}
};
if (!StringUtils.isSet(taskName)) {
job.setSystem(true);
job.setUser(false);
}
return job;
}
}