RangedListView.java

/*******************************************************************************
 * Copyright (c) 2021 Handy Tools for Distributed Computing (HanDist) project.
 *
 * This program and the accompanying materials are made available to you under
 * the terms of the Eclipse Public License 1.0 which accompanies this
 * distribution,
 * and is available at https://www.eclipse.org/legal/epl-v10.html
 *
 * SPDX-License-Identifier: EPL-1.0
 ******************************************************************************/
package handist.collections;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Iterator;
import java.util.List;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.KryoSerializable;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

/**
 * {@link RangedListView} provides an access to a {@link Chunk} restricted to a
 * specific range.
 *
 * @param <T> type handled by the {@link RangedListView} this instance provides
 *            access to
 */
public class RangedListView<T> extends RangedList<T> implements Serializable, KryoSerializable {

    /** Serial Version UID */
    private static final long serialVersionUID = 8258165981421352660L;

    /**
     * Creates a new {@link RangedListView} which does not allow access to any
     * portion of any {@link RangedList}
     *
     * @param <T> type handled by this instance
     * @return a newly created {@link RangedListView} which does not grant any
     *         access
     */
    public static <T> RangedListView<T> emptyView() {
        return new RangedListView<>((Chunk<T>) null, new LongRange(0, 0));
    }

    /** Chunk instance whose access is controlled by this instance */
    private Chunk<T> base;

    /**
     * The range of the {@link RangedList} which this object allows access to
     */
    protected LongRange range;

    /**
     * Creates a new {@link RangedListView} which grants access to the provided
     * {@link RangedList} only on the specified range.
     * <p>
     * The provided base can either be a {@link Chunk} or an existing
     * {@link RangedListView}, in which case the {@link Chunk} base of this
     * {@link RangedListView} will be extracted.
     *
     * @param base  {@link RangedList} this instance will control access to
     * @param range the range of indices that the created instance allows access to
     */
    public RangedListView(RangedList<T> base, LongRange range) {
        this.range = range;
        if (base == null) {
            this.base = null;
            return;
        }
        if (base instanceof Chunk) {
            this.base = (Chunk<T>) base;
        } else if (base instanceof RangedListView) {
            this.base = ((RangedListView<T>) base).base; // base;
        } else {
            throw new UnsupportedOperationException("not supported class: " + base.getClass());
        }
        if (!base.getRange().contains(range)) {
            throw new IndexOutOfBoundsException("[RangeListView] " + range + " is not contained in " + base.getRange());
        }
    }
//
//	@Override
//	public void clear() {
//		throw new UnsupportedOperationException();
//	}

    /**
     * Creates a new {@link RangedList} which contains clones of the elements this
     * {@link RangedListView} grants access to.
     */
    @Override
    public RangedList<T> clone() {
        return cloneRange(range);
    }

    /**
     * Creates a new {@link RangedList} on the specified range containing clones of
     * the elements this view grants access to. The specified range must be included
     * into this {@link RangedListView}'s range.
     */
    @Override
    public RangedList<T> cloneRange(LongRange range) {
        rangeCheck(range);
        if (range.equals(base.getRange())) {
            return base.clone();
        }
        return base.cloneRange(range);
    }

    /**
     * Checks if the provided object is contained in the {@link RangedList} this
     * instance provided access to on the specific indices this instance allows
     * access to. If the underlying {@link RangedList} contains the specified object
     * at an index that this {@link RangedListView} does not grant access to, this
     * method will return false
     */
    @Override
    public boolean contains(Object o) {
        for (long i = range.from; i < range.to; i++) {
            final T elem = base.get(i);
            if (o == null ? elem == null : o.equals(elem)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean equals(Object o) {
        return RangedList.equals(this, o);
    }

    /**
     * Get the element at the provided {@code index}.
     *
     * @param index index of the element to retrieve
     * @return the value stored at the given index
     */
    @Override
    public T get(long index) {
        rangeCheck(index);
        return base.get(index);
    }

    /**
     * Returns the range this instance allows access to
     */
    @Override
    public LongRange getRange() {
        return range;
    }

    @Override
    public int hashCode() {
        return RangedList.hashCode(this);
    }
    @Override
    public Iterator<T> iterator() {
        if(base==null || this.getRange().size()==0) return new ChunkIterator<>();
        return base.subIterator(this.getRange());
    }

    /**
     * Returns a new iterator on the elements of the RangedList this instance
     * provides access to.
     */
    @Override
    public RangedListIterator<T> listIterator() {
        if(base==null||this.getRange().size()==0) return new ChunkListIterator<>();
        return base.subListIterator(this.getRange());
    }

    /**
     * Returns a new iterator which starts at the provided index
     *
     * @param l starting index of the iterator
     * @return iterator on the elements this {@link RangedListView} grants access to
     *         starting at the specified index
     */
    public RangedListIterator<T> listIterator(long l) {
        if(base==null) return new ChunkListIterator<>();
        return base.subListIterator(getRange(), l);
    }
    @Override
    public void read(Kryo kryo, Input input) {
        @SuppressWarnings("unchecked")
        final Chunk<T> chunk = (Chunk<T>) kryo.readClassAndObject(input);
        this.base = chunk;
        this.range = chunk.getRange();
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        @SuppressWarnings("unchecked")
        final Chunk<T> chunk = (Chunk<T>) in.readObject();
        this.base = chunk;
        this.range = chunk.getRange();
    }

    /**
     * Set the given value at the specified index.
     *
     * @param index index at which the value should be set
     * @param v     value to set at the specified index
     * @return old value at the specified index, or {@code null} if there was no
     *         previous vale or the previous value was {@code null}
     * @throws IndexOutOfBoundsException if the given index is out of the range
     *                                   allowed by the view
     */
    @Override
    public T set(long index, T v) {
        rangeCheck(index);
        return base.set(index, v);
    }

    /**
     * Returns the number of indices this {@link RangedListView} provides access to
     */
    @Override
    public long size() {
        return super.size();
    }

    @Override
    protected Iterator<T> subIterator(LongRange range) {
        if(base==null || (range.size()==0)) return new ChunkIterator<>();
        LongRange subrange = getRange().intersection(range);
        if(subrange==null) throw new IndexOutOfBoundsException();
        return base.subIterator(subrange);
    }
    @Override
    protected RangedListIterator<T> subListIterator(LongRange range) {
        if(base==null) return new ChunkListIterator<>();
        LongRange subrange = getRange().intersection(range);
        if(subrange==null) throw new IndexOutOfBoundsException();
        return base.subListIterator(subrange);
    }
    @Override
    protected RangedListIterator<T> subListIterator(LongRange range, long l) {
        if(base==null) return new ChunkListIterator<>();
        LongRange subrange = getRange().intersection(range);
        if(subrange==null) throw new IndexOutOfBoundsException();
        return base.subListIterator(subrange, l);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public Object[] toArray() {
        return toArray(range);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object[] toArray(LongRange range) {
        rangeCheck(range);
        return base.toArray(range);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Chunk<T> toChunk(LongRange range) {
        rangeCheck(range);
        return base.toChunk(range);
    }

    @Override
    public List<T> toList(LongRange r) {
        rangeCheck(r);
        return base.toList(r);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        if (range == null) {
            return "RangedListView under construction";
        }
        sb.append("[" + range + "]");
        final long sz = Config.omitElementsToString ? Math.min(size(), Config.maxNumElementsToString) : size();
        long c = 0;
        for (long i = range.from; i < range.to; i++) {
            if (c++ > 0) {
                sb.append(",");
            }
            sb.append("" + base.get(i));
            if (c == sz) {
                break;
            }
        }
        if (sz < size()) {
            sb.append("...(omitted " + (size() - sz) + " elements)");
        }
        // sb.append("@" + range.begin + ".." + last() + "]");
        return sb.toString();
    }

    @Override
    public void write(Kryo kryo, Output output) {
        final Chunk<T> chunk = this.toChunk(range);
        kryo.writeClassAndObject(output, chunk);
    }

    // TODO this implement generates redundant RangedListView at receiver node.
    private void writeObject(ObjectOutputStream out) throws IOException {
        final Chunk<T> chunk = this.toChunk(range);
        out.writeObject(chunk);
    }



}