All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.nifi.util.RingBuffer Maven / Gradle / Ivy

Go to download

This nifi-utils module should be a general purpose place to store widely and generally useful functions that any component might want to leverage. NO DEPENDENCIES should be added. This module is likely to be leveraged by every extension and should not bring along any other dependencies. The only dependency intended is the nifi-api and even this is expected to be already provided in any case where it would be used. The typical place this util would be found is within a nar and all nars already have nifi-api as a parent dependency. The nifi-api can be thought of as a NiFi Application Container level dependency.

There is a newer version: 2.0.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.nifi.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * Thread-safe implementation of a RingBuffer
 *
 */
public class RingBuffer {

    private final Object[] buffer;
    private int insertionPointer = 0;
    private boolean filled = false;

    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();

    public RingBuffer(final int size) {
        buffer = new Object[size];
    }

    /**
     * Adds the given value to the RingBuffer and returns the value that was removed in order to make room.
     *
     * @param value the new value to add
     * @return value previously in the buffer
     */
    @SuppressWarnings("unchecked")
    public T add(final T value) {
        Objects.requireNonNull(value);

        writeLock.lock();
        try {
            final Object removed = buffer[insertionPointer];

            buffer[insertionPointer] = value;

            if (insertionPointer == buffer.length - 1) {
                filled = true;
            }

            insertionPointer = (insertionPointer + 1) % buffer.length;
            return (T) removed;
        } finally {
            writeLock.unlock();
        }
    }

    public int getSize() {
        readLock.lock();
        try {
            return filled ? buffer.length : insertionPointer;
        } finally {
            readLock.unlock();
        }
    }

    public List getSelectedElements(final Filter filter) {
        return getSelectedElements(filter, Integer.MAX_VALUE);
    }

    public List getSelectedElements(final Filter filter, final int maxElements) {
        final List selected = new ArrayList<>(1000);
        int numSelected = 0;
        readLock.lock();
        try {
            for (int i = 0; i < buffer.length && numSelected < maxElements; i++) {
                final int idx = (insertionPointer + i) % buffer.length;
                final Object val = buffer[idx];
                if (val == null) {
                    continue;
                }

                @SuppressWarnings("unchecked")
                final T element = (T) val;
                if (filter.select(element)) {
                    selected.add(element);
                    numSelected++;
                }
            }
        } finally {
            readLock.unlock();
        }
        return selected;
    }

    public int countSelectedElements(final Filter filter) {
        int numSelected = 0;
        readLock.lock();
        try {
            for (int i = 0; i < buffer.length; i++) {
                final int idx = (insertionPointer + i) % buffer.length;
                final Object val = buffer[idx];
                if (val == null) {
                    continue;
                }

                @SuppressWarnings("unchecked")
                final T element = (T) val;
                if (filter.select(element)) {
                    numSelected++;
                }
            }
        } finally {
            readLock.unlock();
        }

        return numSelected;
    }

    /**
     * Removes all elements from the RingBuffer that match the given filter
     *
     * @param filter to use for deciding what is removed
     * @return always zero
     */
    public int removeSelectedElements(final Filter filter) {
        int count = 0;

        writeLock.lock();
        try {
            for (int i = 0; i < buffer.length; i++) {
                final int idx = (insertionPointer + i + 1) % buffer.length;
                final Object val = buffer[idx];
                if (val == null) {
                    continue;
                }

                @SuppressWarnings("unchecked")
                final T element = (T) val;

                if (filter.select(element)) {
                    buffer[idx] = null;
                }
            }
        } finally {
            writeLock.unlock();
        }

        return count;
    }

    public List asList() {
        return getSelectedElements(new Filter() {
            @Override
            public boolean select(final T value) {
                return true;
            }
        });
    }

    public T getOldestElement() {
        readLock.lock();
        try {
            return getElementData(insertionPointer);
        } finally {
            readLock.unlock();
        }
    }

    public T getNewestElement() {
        readLock.lock();
        try {
            int index = (insertionPointer == 0) ? buffer.length - 1 : insertionPointer - 1;
            return getElementData(index);
        } finally {
            readLock.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    private T getElementData(final int index) {
        readLock.lock();
        try {
            return (T) buffer[index];
        } finally {
            readLock.unlock();
        }
    }

    /**
     * Iterates over each element in the RingBuffer, calling the {@link ForEachEvaluator#evaluate(Object) evaluate} method on each element in the RingBuffer. If the Evaluator returns {@code false},
     * the method will skip all remaining elements in the RingBuffer; otherwise, the next element will be evaluated until all elements have been evaluated.
     *
     * @param evaluator used to evaluate each item in the ring buffer
     */
    public void forEach(final ForEachEvaluator evaluator) {
        forEach(evaluator, IterationDirection.FORWARD);
    }

    /**
     * Iterates over each element in the RingBuffer, calling the {@link ForEachEvaluator#evaluate(Object) evaluate} method on each element in the RingBuffer. If the Evaluator returns {@code false},
     * the method will skip all remaining elements in the RingBuffer; otherwise, the next element will be evaluated until all elements have been evaluated.
     *
     * @param evaluator the evaluator
     * @param iterationDirection the order in which to iterate over the elements in the RingBuffer
     */
    public void forEach(final ForEachEvaluator evaluator, final IterationDirection iterationDirection) {
        readLock.lock();
        try {
            final int startIndex;
            final int endIndex;
            final int increment;

            if (iterationDirection == IterationDirection.FORWARD) {
                startIndex = 0;
                endIndex = buffer.length - 1;
                increment = 1;
            } else {
                startIndex = buffer.length - 1;
                endIndex = 0;
                increment = -1;
            }

            for (int i = startIndex; (iterationDirection == IterationDirection.FORWARD ? i <= endIndex : i >= endIndex); i += increment) {
                final int idx = (insertionPointer + i) % buffer.length;
                final Object val = buffer[idx];
                if (val == null) {
                    continue;
                }

                @SuppressWarnings("unchecked")
                final T element = (T) val;
                if (!evaluator.evaluate(element)) {
                    return;
                }
            }
        } finally {
            readLock.unlock();
        }
    }

    public static interface Filter {

        boolean select(S value);
    }

    /**
     * Defines an interface that can be used to iterate over all of the elements in the RingBuffer via the {@link #forEach} method
     *
     * @param  the type to evaluate
     */
    public static interface ForEachEvaluator {

        /**
         * Evaluates the given element and returns {@code true} if the next element should be evaluated, {@code false} otherwise
         *
         * @param value the value to evaluate
         * @return true if should continue evaluating; false otherwise
         */
        boolean evaluate(S value);
    }

    public static enum IterationDirection {

        FORWARD,
        BACKWARD;
    }
}