/**
* Copyright 2012 Comcast Corporation
*
* 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.comcast.cmb.common.util;
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.Logger;
/**
* A utility class used to capture objects in a rolling window fashion
* Note: This class should not be used if there are a lot of adding of events
* instead callers should get the latest node and modify that in most cases
*
* @author aseem
* Class is thread-safe
*/
public final class RollingWindowCapture<T extends RollingWindowCapture.PayLoad> {
private static Logger logger = Logger.getLogger(RollingWindowCapture.class);
private final long _windowSizeSec;
private final int _tolerance;
private final AtomicInteger _toleranceCount;
private class PayLoadNode {
public final T _payLoad;
private final long _captureTime;
public PayLoadNode(T payload, long captureTime) {
_payLoad = payload;
_captureTime = captureTime;
}
}
//internally we used a CopyOnWriteArrayList to implement the rolling window in chronological order.
//First node is the oldest node
//We use CopyOnWriteArrayList since its the only standard data-structure provided that offers concurrent
//access to its elements and provides the List interface, specifically get(index) method. We need this
//method to return the tail of the rollingwindow.
private CopyOnWriteArrayList<PayLoadNode> _queue = new CopyOnWriteArrayList<PayLoadNode>();
public static interface Visitor<T> {
public void processNode(T n);
}
/**
* The payl0ad to capture in window
*
*/
public static abstract class PayLoad {}
/**
*
* @param windowSizeSec the size of rolling window in seconds
* @param tolerance the number of adds you can go over before must reduce the window
*/
public RollingWindowCapture(int windowSizeSec, int tolerance) {
_windowSizeSec = windowSizeSec;
_tolerance = tolerance;
_toleranceCount = new AtomicInteger();
}
/**
* Method would traverse the entire list of elements in the window and call
* the visitor on each node
* @param v the Visitor impl
*/
public void visitAllNodes(Visitor<T> v) {
cleanupWindow();
Iterator<PayLoadNode> it = _queue.iterator();
while (it.hasNext()) {
PayLoadNode node = it.next();
v.processNode(node._payLoad);
}
}
/**
* Add current time to the header of list
* Note: This is an expensive operation O(n) complexity where n is the number of elements in the list
*/
public void addNow(T payLoad) {
PayLoadNode node = new PayLoadNode(payLoad, System.currentTimeMillis());
_queue.add(node);
if (_toleranceCount.incrementAndGet() >= _tolerance) {
cleanupWindow();
}
}
/**
* @return The last added payload or null if no element
*/
public T getLatestPayload() {
boolean success = false;
while (!success) {
if (_queue.size() == 0) return null;
try {
PayLoadNode head = _queue.get(_queue.size() - 1);
success = true;
return head._payLoad;
} catch (IndexOutOfBoundsException e) {}
}
return null;
}
/**
* Go through the beginning of the queue and remove all expired nodes
*/
private void cleanupWindow() {
long now = System.currentTimeMillis();
Iterator<PayLoadNode> it = _queue.iterator();
while (it.hasNext()) {
PayLoadNode node = it.next();
if (node._captureTime + (_windowSizeSec*1000) < now) {
if (!_queue.remove(node)) {
logger.debug("event=cleanup_window status=queue_did_not_change info=node_may_have_been_deleted");
}
} else {
//found the first node in the capture window. we are done
_toleranceCount.set(0);
return;
}
}
_toleranceCount.set(0);
}
}