UnsafeList.java

/*
 *                      ..::jDrawingLib::..
 *
 * Copyright (C) Federico Vera 2012 - 2023 <[email protected]>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.dkt.graphics.elements;

import java.util.Arrays;
import java.util.Iterator;
import java.util.Objects;

/**
 * This class creates an unsafe list, actually the only thing this list is
 * unsafe against is {@code null} values and the {@link Iterator} doesn't check
 * anything. But at least it's thread safe.<br>
 * <i>Note:</i> This class does not implement the {@link java.util.List}
 * interface.
 *
 * @author Federico Vera {@literal<[email protected]>}
 */
class UnsafeList implements Iterable<GraphicE> {
    private final Object mutex = new Object();
    private GraphicE[] elements;
    private int cursor;

    /**
     * Constructs a new {@code UnsafeList} of a given size
     *
     * @param size initial capacity of the list
     */
    public UnsafeList(int size) {
        elements = new GraphicE[size];
    }

    /**
     * Adds a new element to the list.<br>
     * If the list is full, it's internal size will be incremented by 10%
     *
     * @param elm {@link GraphicE} to add
     * @return {@code true} if the element was added, and {@code false} if the
     * element was omitted (it will be omitted if it's {@code null})
     */
    public boolean add(GraphicE elm) {
        if (elm == null) {
            return false;
        }

        synchronized(mutex) {
            ensureCapacity((cursor + 1) * 110 / 100);
            elements[cursor++] = elm;

            return true;
        }
    }

    /**
     * Reserves more size for elements. This value will be omitted if it's less
     * than the current capacity.
     *
     * @param capacity new capacity
     */
    public void ensureCapacity(int capacity) {
        synchronized(mutex) {
            if (capacity > elements.length) {
                final GraphicE[] foo = new GraphicE[capacity];
                System.arraycopy(elements, 0, foo, 0, elements.length);
                elements = foo;
            }
        }
    }

    /**
     * Retrieves an element from a given index, it will return {@code null} if
     * the index doesn't exist
     *
     * @param idx Element index
     * @return element at a given index or {@code null} otherwise
     */
    public GraphicE get(int idx) {
        synchronized(mutex) {
            try {
                return elements[idx];
            } catch (Exception e){
                return null;
            }
        }
    }

    /**
     * Retrieves the current number of elements of the list
     *
     * @return number of elements
     */
    public int size() {
        return cursor;
    }

    /**
     * Tells if the list is empty
     *
     * @return {@code true} if the list is empty and {@code false} otherwise
     */
    public boolean isEmpty() {
        return cursor == 0;
    }

    /**
     * Clears the list
     */
    public void clear() {
        synchronized(mutex) {
            Arrays.fill(elements, null);
            cursor = 0;
        }
    }

    /**
     * Retrieves the first index of the element in the list
     *
     * @param elm element to check
     * @return index of the element or {@code -1} if the element wasn't found
     */
    public int indexOf(GraphicE elm) {
        synchronized(mutex) {
            for (int i = 0; i < cursor; i++) {
                if (Objects.equals(elements[i], elm)) {
                    return i;
                }
            }

            return -1;
        }
    }

    /**
     * Removes an element form a given index, if the index doesn't exist then
     * it does nothing.
     *
     * @param idx index of the element to remove
     * @return {@code GraphicE} that was removed, and {@code null} in case
     * nothing was found
     */
    public GraphicE remove(int idx) {
        synchronized(mutex) {
            final GraphicE elm = get(idx);

            if (elm == null) {
                return null;
            }

            final int numMoved = cursor - idx - 1;

            if (numMoved > 0) {
                System.arraycopy(elements, idx + 1, elements, idx, numMoved);
            }

            elements[--cursor] = null;

            return elm;
        }
    }

    /**
     * Removes all the instances of a {@code GraphicE} from the list
     *
     * @param e element to remove
     * @return {@code true} if an element was removed and {@code false} if the
     * element wasn't found
     */
    public boolean remove(GraphicE e) {
        synchronized(mutex) {
            boolean status = false;
            while (remove(indexOf(e)) != null) status = true;
            return status;
        }
    }

    /**
     * Tells if a {@code GraphicE} is contained in the list
     *
     * @param e element to check
     * @return {@code true} if the element is contained in the list and {@code
     * false} otherwise
     */
    public boolean contains(GraphicE e) {
        synchronized(mutex) {
            return indexOf(e) != -1;
        }
    }

    /**
     * Adds all the elements in a list to this one
     *
     * @param elmts {@code UnsafeList} to add
     */
    public void addAll(UnsafeList elmts) {
        if (elmts == null || this == elmts) {
            return;
        }

        synchronized(mutex) {
            ensureCapacity(elmts.size() + this.size());
            System.arraycopy(elmts.elements, 0, elements, cursor, elmts.size());
            cursor += elmts.size();
        }
    }

    @Override
    public int hashCode() {
        int hash = 7;
        for (int i = 0; i < size(); i++) {
            hash = 79 * hash + elements[i].hashCode(); 
        }
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        
        if (obj == null) {
            return false;
        }
        
        if (getClass() != obj.getClass()) {
            return false;
        }
        
        final UnsafeList other = (UnsafeList) obj;
        if (size() != other.size()) {
            return false;
        }
        
        for (int i = 0; i < size(); i++) {
            if (!Objects.equals(elements[i], other.elements[i])) {
                return false;
            }
        }
        
        return true;
    }

    @Override
    public Iterator<GraphicE> iterator() {
        return new Iterator<>() {
            private int idx = -1;

            @Override
            public boolean hasNext() {
                return idx != cursor - 1;
            }

            @Override
            public GraphicE next() {
                try {
                    return elements[++idx];
                } catch (Exception e) {
                    return null;
                }
            }

            @Override
            public void remove() {
                //We don't need this in our current implementation
            }
        };
    }

}