package cern.laser.util.buffer; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.log4j.Logger; /** A buffering utility class. * @author F.Calderini */ public class SynchroBuffer { private static final Logger LOGGER = Logger.getLogger(SynchroBuffer.class.getName()); private long minWindowSize; private long maxWindowSize; private int windowGrowthFactor; private int duplicatePolicy; private Thread checkingThread; private AtomicBoolean closed = new AtomicBoolean(false); private AtomicBoolean firing = new AtomicBoolean(false); private AtomicBoolean enabled = new AtomicBoolean(false); private SynchroBufferListener listener = null; /** The buffer */ private List buffer = null; /** Allows object duplication. */ public static final int DUPLICATE_OK = 1; /** Replaces duplicated objects. */ public static final int DUPLICATE_REPLACE = 2; /** Discards duplicated objects. */ public static final int DUPLICATE_DISCARD = 3; /** Constructor. * @param minWindowSize the buffer window min size (msec) * @param maxWindowSize the buffer window max size (msec) * @param windowGrowthFactor the buffer window growth factor (size = minWindowSize + msg/sec x windowGrowthFactor) * @param duplicatePolicy the buffer object duplication policy */ public SynchroBuffer(long minWindowSize, long maxWindowSize, int windowGrowthFactor, int duplicatePolicy) { init(minWindowSize, maxWindowSize, windowGrowthFactor, duplicatePolicy); } /** Default constructor. Initialisation is made via properties. * It reads the configuration from the resource config file specified via the system * property <code>syncrobuffer.properties</code>. If not defined, it looks for the default config file * <code>synchrobuffer-config.properties</code>. System properties override the configuration loaded from * the properties file. Configuration properties are : * <UL> * <LI>synchrobuffer.minwindowsize</LI> (msec, default 500) * <LI>synchrobuffer.maxwindowsize</LI> (msec, default 5000) * <LI>synchrobuffer.windowgrowthfactor</LI> (windowSize = minWindowSize + msg/sec x windowGrowthFactor, default 100) * <LI>synchrobuffer.duplicatepolicy</LI> (default SynchroBuffer.DUPLICATES_OK) * </UL> */ public SynchroBuffer() { Properties properties = SynchroBufferConfig.getProperties(this.getClass().getClassLoader()); long min_window_size = Long.parseLong(properties.getProperty(SynchroBufferConfig.MIN_WINDOW_SIZE_PROPERTY)); long max_window_size = Long.parseLong(properties.getProperty(SynchroBufferConfig.MAX_WINDOW_SIZE_PROPERTY)); int window_growth_factor = Integer.parseInt(properties.getProperty(SynchroBufferConfig.WINDOW_GROWTH_FACTOR_PROPERTY)); int duplicate_policy = Integer.parseInt(properties.getProperty(SynchroBufferConfig.DUPLICATE_POLICY_PROPERTY)); init(min_window_size, max_window_size, window_growth_factor, duplicate_policy); } private void init(long minSize, long maxSize, int growthFactor, int policy) { System.out.println("SynchroBuffer[minWindowSize=" + minSize + ",maxWindowSize=" + maxSize + ",windowGrowthFactor=" + growthFactor + ",duplicatePolicy=" + (policy == SynchroBuffer.DUPLICATE_DISCARD ? "DUPLICATE_DISCARD" : (policy == SynchroBuffer.DUPLICATE_REPLACE ? "DUPLICATE_REPLACE" : "DUPLICATE_OK")) + "]"); if ( (minSize <= 0) || (maxSize <= 0) || (growthFactor <= 0) ) { throw(new IllegalArgumentException("arguments must be greater than zero")); } else if ( maxSize <= minSize ) { throw(new IllegalArgumentException("maximum window size must be greater than minimum window size")); } else { this.minWindowSize = minSize; this.maxWindowSize = maxSize; this.windowGrowthFactor = growthFactor; this.duplicatePolicy = policy; buffer = new ArrayList(); checkingThread = createCheckingThread(); checkingThread.start(); } } private Thread createCheckingThread() { return new Thread() { public void run() { float objects_per_sec; long calculated_window_size; long firing_time = 0; long wait_time = minWindowSize; while ( (!closed.get()) || (!isEmpty() && enabled.get()) ) { if (enabled.get()) { objects_per_sec = (1000 * buffer.size()) / (wait_time + firing_time); calculated_window_size = minWindowSize + ((long)(windowGrowthFactor * objects_per_sec)); wait_time = ( (calculated_window_size < maxWindowSize) ? calculated_window_size : maxWindowSize ); firing_time = fire(); try { Thread.sleep(wait_time); } catch (InterruptedException ie) {} } else { try { Thread.sleep(maxWindowSize); } catch (InterruptedException ie) {} } } } }; } private long fire() { firing.set(true); Collection pulled = null; synchronized(buffer) { pulled = (Collection) ((ArrayList)buffer).clone(); buffer.clear(); } long time_before = System.currentTimeMillis(); if (listener != null) { if (pulled.size() > 0) { try { listener.pull(new PullEvent(this, pulled)); } catch (PullException pe) { pe.printStackTrace(); } } } long time_after = System.currentTimeMillis(); long time_elapsed = time_after-time_before; firing.set(false); return time_elapsed; } /** Push an object into the buffer. If the duplicate policy is DUPLICATE_DISCARD the object * is discarded if the buffer already contains it. If the duplicate policy is DUPLICATE_REPLACE * the object replaces any previously pushed duplicated instance. The object is appended otherwise. * Equals method is used to determine duplications. * @param o the object to push */ public void push(Object object) { if (closed.get()) { throw new IllegalArgumentException("buffer closed"); } synchronized(buffer) { switch (duplicatePolicy) { case SynchroBuffer.DUPLICATE_DISCARD : if (!buffer.contains(object)) { buffer.add(object); } break; case SynchroBuffer.DUPLICATE_REPLACE : int index = buffer.indexOf(object); if (index == -1) { buffer.add(object); } else { buffer.set(index, object); } break; default : buffer.add(object); }; } } /** Push a collection of objects into the buffer. * @param collection the collection of objects to push */ public void push(Collection collection) { if (closed.get()) { throw new IllegalArgumentException("buffer closed"); } if ( (collection != null) && (collection.size() != 0) ) { synchronized(buffer) { if ( (duplicatePolicy != SynchroBuffer.DUPLICATE_DISCARD) && (duplicatePolicy != SynchroBuffer.DUPLICATE_REPLACE) ) { buffer.addAll(collection); } else { Iterator iterator = collection.iterator(); while (iterator.hasNext()) { push(iterator.next()); } } } } } /** Set the buffer consumer listener. * @param listener the listener */ public void setSynchroBufferListener(SynchroBufferListener listener) { this.listener = listener; } /** Enable the listener. The listener is disabled by default. */ public void enable() { enabled.set(true); } /** Disable the listener. Pushed object are kept in the buffer and delivered when the listener is enabled. */ public void disable() { enabled.set(false); } private boolean isEmpty() { synchronized(buffer) { return buffer.isEmpty(); } } /** Close the buffer and deallocate resources. */ public void close() { boolean wasClosed=closed.getAndSet(true); if (wasClosed) { return; } while ((!isEmpty() && enabled.get()) || firing.get()) { try { Thread.sleep(minWindowSize); } catch (Exception e) {} } } }