/*
 * Copyright 2015 Open Networking Laboratory
 *
 * 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 org.onlab.util;

import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

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

/**
 * Base implementation of an item accumulator. It allows triggering based on
 * item inter-arrival time threshold, maximum batch life threshold and maximum
 * batch size.
 */
public abstract class AbstractAccumulator<T> implements Accumulator<T> {

    private Logger log = LoggerFactory.getLogger(AbstractAccumulator.class);

    private final Timer timer;
    private final int maxItems;
    private final int maxBatchMillis;
    private final int maxIdleMillis;

    private TimerTask idleTask = new ProcessorTask();
    private TimerTask maxTask = new ProcessorTask();

    private List<T> items = Lists.newArrayList();

    /**
     * Creates an item accumulator capable of triggering on the specified
     * thresholds.
     *
     * @param timer               timer to use for scheduling check-points
     * @param maxItems            maximum number of items to accumulate before
     *                            processing is triggered
     * @param maxBatchMillis      maximum number of millis allowed since the first
     *                            item before processing is triggered
     * @param maxIdleMillis       maximum number millis between items before
     *                            processing is triggered
     */
    protected AbstractAccumulator(Timer timer, int maxItems,
                                  int maxBatchMillis, int maxIdleMillis) {
        this.timer = checkNotNull(timer, "Timer cannot be null");

        checkArgument(maxItems > 1, "Maximum number of items must be > 1");
        checkArgument(maxBatchMillis > 0, "Maximum millis must be positive");
        checkArgument(maxIdleMillis > 0, "Maximum idle millis must be positive");

        this.maxItems = maxItems;
        this.maxBatchMillis = maxBatchMillis;
        this.maxIdleMillis = maxIdleMillis;
    }

    @Override
    public synchronized void add(T item) {
        idleTask = cancelIfActive(idleTask);
        items.add(checkNotNull(item, "Item cannot be null"));

        // Did we hit the max item threshold?
        if (items.size() >= maxItems) {
            maxTask = cancelIfActive(maxTask);
            schedule(1);
        } else {
            // Otherwise, schedule idle task and if this is a first item
            // also schedule the max batch age task.
            idleTask = schedule(maxIdleMillis);
            if (items.size() == 1) {
                maxTask = schedule(maxBatchMillis);
            }
        }
    }

    // Schedules a new processor task given number of millis in the future.
    private TimerTask schedule(int millis) {
        TimerTask task = new ProcessorTask();
        timer.schedule(task, millis);
        return task;
    }

    // Cancels the specified task if it is active.
    private TimerTask cancelIfActive(TimerTask task) {
        if (task != null) {
            task.cancel();
        }
        return task;
    }

    // Task for triggering processing of accumulated items
    private class ProcessorTask extends TimerTask {
        @Override
        public void run() {
            idleTask = cancelIfActive(idleTask);
            if (isReady()) {
                try {
                    maxTask = cancelIfActive(maxTask);
                    List<T> items = finalizeCurrentBatch();
                    if (!items.isEmpty()) {
                        processItems(items);
                    }
                } catch (Exception e) {
                    log.warn("Unable to process batch due to {}", e);
                }
            } else {
                idleTask = schedule(maxIdleMillis);
            }
        }
    }

    // Demotes and returns the current batch of items and promotes a new one.
    private synchronized List<T> finalizeCurrentBatch() {
        List<T> toBeProcessed = items;
        items = Lists.newArrayList();
        return toBeProcessed;
    }

    @Override
    public boolean isReady() {
        return true;
    }

    /**
     * Returns the backing timer.
     *
     * @return backing timer
     */
    public Timer timer() {
        return timer;
    }

    /**
     * Returns the maximum number of items allowed to accumulate before
     * processing is triggered.
     *
     * @return max number of items
     */
    public int maxItems() {
        return maxItems;
    }

    /**
     * Returns the maximum number of millis allowed to expire since the first
     * item before processing is triggered.
     *
     * @return max number of millis a batch is allowed to last
     */
    public int maxBatchMillis() {
        return maxBatchMillis;
    }

    /**
     * Returns the maximum number of millis allowed to expire since the last
     * item arrival before processing is triggered.
     *
     * @return max number of millis since the last item
     */
    public int maxIdleMillis() {
        return maxIdleMillis;
    }

}

@@ -1408,32 +1408,41 @@
 s;%0A%0A    private 
+volatile 
 TimerTask idleTa
@@ -1471,32 +1471,41 @@
 ();%0A    private 
+volatile 
 TimerTask maxTas
@@ -3987,24 +3987,82 @@
 oid run() %7B%0A
+            synchronized (AbstractAccumulator.this) %7B%0A    
             
@@ -4090,32 +4090,46 @@
 tive(idleTask);%0A
+            %7D%0A
             if (
@@ -4163,16 +4163,82 @@
   try %7B%0A
+                    synchronized (AbstractAccumulator.this) %7B%0A    
         
@@ -4276,32 +4276,54 @@
 ctive(maxTask);%0A
+                    %7D%0A
                 
@@ -4618,32 +4618,94 @@
        %7D else %7B%0A
+                synchronized (AbstractAccumulator.this) %7B%0A    
                 
@@ -4732,32 +4732,50 @@
 maxIdleMillis);%0A
+                %7D%0A
             %7D%0A  

Fix for ONOS-2572 - Excessive events delivered by AbstractAccumulator