package nl.tno.sensorstorm.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.TreeSet; import nl.tno.sensorstorm.api.particles.MetaParticle; import nl.tno.sensorstorm.api.particles.Particle; import nl.tno.sensorstorm.api.processing.Operation; /** * Class that synchronizes {@link Particle}s coming from multiple (probably * parallel executing) sources and filters out duplicate {@link MetaParticle}s. * This buffer introduces a configurable delay. * <p> * When the delay is 0 the buffer will always immediately return the same * particle. Warning: When the size is 0, duplicate MetaParticles will not be * filtered out! */ public class SyncBuffer { private static final int INITIAL_CAPACITY_RETURN_LIST = 5; /** * Special {@link Comparator} used by the {@link SyncBuffer}. It sorts * {@link Particle}s based on their timestamp. Additionally, duplicate * {@link MetaParticle}s (with possibly a different originId) will return 0, * which results in removing the duplicates from the TreeSet. */ private static class ParticleComparator implements Comparator<Particle> { @Override public int compare(Particle p1, Particle p2) { int comp = Long.compare(p1.getTimestamp(), p2.getTimestamp()); if (comp == 0) { // Same timestamp if ((p1 instanceof MetaParticle) && (p2 instanceof MetaParticle) && ((MetaParticle) p1) .equalMetaParticle((MetaParticle) p2)) { // This will filter out duplicate MetaParticles return 0; } else { // Arbitrary ordering return System.identityHashCode(p1) - System.identityHashCode(p2); } } return comp; } } private final long bufferSizeMs; private final TreeSet<Particle> particles; private long lastSentOutTimestamp; /** * Create a new {@link SyncBuffer} with a predefined buffer time. A value of * 0 will effectively disable the buffer, and disable the filtering of * duplicate {@link MetaParticle}s. * * @param bufferSizeMs * Amount of time {@link Particle}s are kept in the buffer in * milliseconds */ public SyncBuffer(long bufferSizeMs) { if (bufferSizeMs < 0) { throw new IllegalArgumentException("buffer size cannot be negative"); } this.bufferSizeMs = bufferSizeMs; lastSentOutTimestamp = -1; particles = new TreeSet<Particle>(new ParticleComparator()); } /** * Push a new {@link Particle} in into the {@link SyncBuffer}. When pushing * a {@link Particle}, zero or more {@link Particle}s will leave the buffer * and can be processed by the {@link Operation}. When a {@link Particle} is * too late to be synchronized it will be rejected. * * @param particle * {@link Particle} to add to the {@link SyncBuffer} * @return List of {@link Particle} that leave the buffer (can be empty) * @throws IllegalArgumentException * When the {@link Particle} is too late to be synchronized in * the buffer */ public List<Particle> pushParticle(Particle particle) { if (bufferSizeMs == 0) { return Collections.singletonList(particle); } if (particle != null) { if (particle.getTimestamp() < lastSentOutTimestamp) { // Reject throw new IllegalArgumentException( "Particle arrived too late, rejected by SyncBuffer"); } particles.add(particle); } ArrayList<Particle> list = new ArrayList<Particle>( INITIAL_CAPACITY_RETURN_LIST); long target = highestTimestamp() - bufferSizeMs; while (lowestTimestamp() <= target) { list.add(particles.pollFirst()); } if (!list.isEmpty()) { lastSentOutTimestamp = list.get(0).getTimestamp(); } return list; } /** * Remove all elements from the {@link SyncBuffer}. * * @return Sorted List of elements that were in the {@link SyncBuffer} */ public List<Particle> flush() { ArrayList<Particle> list = new ArrayList<Particle>(size()); list.addAll(particles); particles.clear(); if (!list.isEmpty()) { lastSentOutTimestamp = list.get(0).getTimestamp(); } return list; } /** * Return the timestamp of the youngest {@link Particle} in the * {@link SyncBuffer}. * * @return Highest timestamp of a {@link Particle} in the buffer */ public long highestTimestamp() { if (size() == 0) { return -1; } return particles.last().getTimestamp(); } /** * Return the timestamp of the oldest {@link Particle} in the * {@link SyncBuffer}. * * @return Lowest timestamp of a {@link Particle} in the buffer */ public long lowestTimestamp() { if (size() == 0) { return -1; } return particles.first().getTimestamp(); } /** * @return Number of {@link Particle}s in the buffer */ public int size() { return particles.size(); } }