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

com.shapesecurity.functional.data.NonEmptyImmutableList Maven / Gradle / Ivy

There is a newer version: 3.1.0
Show newest version
/*
 * Copyright 2014 Shape Security, Inc.
 *
 * 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.shapesecurity.functional.data;

import com.shapesecurity.functional.F;
import com.shapesecurity.functional.F2;
import com.shapesecurity.functional.Pair;

import javax.annotation.Nonnull;
import java.util.ArrayList;

public final class NonEmptyImmutableList extends ImmutableList {
    public static final int HASH_START = HashCodeBuilder.put(HashCodeBuilder.init(), "List");
    @Nonnull
    public final T head;

    @Nonnull
    public final ImmutableList tail;

    protected NonEmptyImmutableList(@Nonnull T head, @Nonnull final ImmutableList tail) {
        super(tail.length + 1);
        this.head = head;
        this.tail = tail;
    }

    @Nonnull
    public ImmutableList tail() {
        return this.tail;
    }

    @Nonnull
    private T[] toObjectArray() {
        int length = this.length;
        Object[] target = new Object[length];
        //noinspection unchecked
        return (T[]) ((ImmutableList) this).toArray(target);
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof NonEmptyImmutableList)) {
            return false;
        }

        // Manually expanded tail recursion
        ImmutableList l = this;
        ImmutableList r = (ImmutableList) o;

        if (l.length != r.length) {
            return false;
        }
        
        while (l instanceof NonEmptyImmutableList && r instanceof NonEmptyImmutableList) {
            if (l == r) {
                return true;
            }
            NonEmptyImmutableList nelL = (NonEmptyImmutableList) l;
            NonEmptyImmutableList nelR = (NonEmptyImmutableList) r;
            if (!nelL.head.equals(nelR.head)) {
                return false;
            }
            l = nelL.tail;
            r = nelR.tail;
        }
        return l == r;
    }

    @Nonnull
    @Override
    public  A foldLeft(@Nonnull F2 f, @Nonnull A init) {
        ImmutableList list = this;
        while (list instanceof NonEmptyImmutableList) {
            init = f.apply(init, ((NonEmptyImmutableList) list).head);
            list = ((NonEmptyImmutableList) list).tail();
        }
        return init;
    }

    @Nonnull
    @Override
    public  A foldRight(@Nonnull F2 f, @Nonnull A init) {
        T[] list = this.toObjectArray();
        for (int i = this.length - 1; i >= 0; i--) {
            init = f.apply(list[i], init);
        }
        return init;
    }

    @Nonnull
    public T reduceLeft(@Nonnull F2 f) {
        return this.tail.foldLeft(f, this.head);
    }

    @Nonnull
    public T reduceRight(@Nonnull F2 f) {
        return this.reverse().reduceLeft(f.flip());
    }

    @Nonnull
    @Override
    public Maybe maybeHead() {
        return Maybe.of(this.head);
    }

    @Nonnull
    @Override
    public Maybe maybeLast() {
        return Maybe.of(this.last());
    }


    @Nonnull
    @Override
    public Maybe> maybeTail() {
        return Maybe.of(this.tail);
    }

    @Nonnull
    @Override
    public Maybe> maybeInit() {
        return Maybe.of(this.init());
    }

    @Nonnull
    public T last() {
        NonEmptyImmutableList other = this;
        while (true) {
            if (other.tail instanceof NonEmptyImmutableList) {
                other = ((NonEmptyImmutableList) other.tail);
            } else {
                return other.head;
            }
        }
    }

    @Nonnull
    public ImmutableList init() {
        return fromBounded(this.toObjectArray(), 0, this.length - 1);
    }

    @Override
    public int count(@Nonnull F f) {
        int count = 0;
        ImmutableList list = this;
        for (int i = 0; i < this.length; i++) {
            if (f.apply(((NonEmptyImmutableList) list).head)) {
                count++;
            }
            list = ((NonEmptyImmutableList) list).tail;
        }
        return count;
    }

    @Nonnull
    @Override
    public ImmutableList filter(@Nonnull F f) {
        @SuppressWarnings("unchecked")
        T[] result = (T[]) new Object[this.length];
        ImmutableList list = this;
        int j = 0;
        for (int i = 0; i < this.length; i++) {
            T el = ((NonEmptyImmutableList) list).head;
            if (f.apply(el)) {
                result[j] = el;
                j++;
            }
            list = ((NonEmptyImmutableList) list).tail;
        }
        return fromBounded(result, 0, j);
    }

    @Nonnull
    @Override
    public  NonEmptyImmutableList map(@Nonnull F f) {
        @SuppressWarnings("unchecked")
        B[] result = (B[]) new Object[this.length];
        ImmutableList list = this;
        for (int i = 0; i < this.length; i++) {
            result[i] = f.apply(((NonEmptyImmutableList) list).head);
            list = ((NonEmptyImmutableList) list).tail;
        }
        return (NonEmptyImmutableList) from(result);
    }

    @Override
    @Nonnull
    public final  NonEmptyImmutableList mapWithIndex(@Nonnull F2 f) {
        int length = this.length;
        @SuppressWarnings("unchecked")
        B[] result = (B[]) new Object[length];
        ImmutableList list = this;
        for (int i = 0; i < length; i++) {
            result[i] = f.apply(i, ((NonEmptyImmutableList) list).head);
            list = ((NonEmptyImmutableList) list).tail();
        }
        return (NonEmptyImmutableList) from(result);
    }

    @Nonnull
    @Override
    public ImmutableList take(int n) {
        if (n <= 0) {
            return empty();
        }
        @SuppressWarnings("unchecked")
        T[] result = (T[]) new Object[n];
        ImmutableList list = this;
        for (int i = 0; i < n; i++) {
            result[i] = ((NonEmptyImmutableList) list).head;
            list = ((NonEmptyImmutableList) list).tail;
        }
        return from(result);
    }

    @Nonnull
    @Override
    public ImmutableList drop(int n) {
        if (n <= 0) {
            return this;
        }
        ImmutableList list = this;
        while (n > 0) {
            if (list instanceof NonEmptyImmutableList) {
                list = ((NonEmptyImmutableList) list).tail;
                n--;
            }
        }
        return list;
    }

    @Nonnull
    @Override
    public Maybe> toNonEmptyList() {
        return Maybe.of(this);
    }

    @Nonnull
    @Override
    public  Maybe decons(@Nonnull F2, B> f) {
        return Maybe.of(f.apply(this.head, this.tail()));
    }

    @Nonnull
    @Override
    public  ImmutableList zipWith(@Nonnull F2 f, @Nonnull ImmutableList list) {
        ImmutableList list1 = this;
        ImmutableList list2 = list;
        int n = Math.min(list1.length, list2.length);
        @SuppressWarnings("unchecked")
        C[] result = (C[]) new Object[n];
        for (int i = 0; i < n; i++) {
            result[i] = f.apply(((NonEmptyImmutableList) list1).head, ((NonEmptyImmutableList) list2).head);
            list1 = ((NonEmptyImmutableList) list1).tail;
            list2 = ((NonEmptyImmutableList) list2).tail;
        }
        return from(result);
    }

    @Override
    public boolean isEmpty() {
        return false;
    }

    @Nonnull
    @Override
    public  ImmutableList append(@Nonnull ImmutableList list) {
        if (list.length == 0) {
            return this;
        }
        T[] copy = this.toObjectArray();
        @SuppressWarnings("unchecked")
        ImmutableList listT = (ImmutableList) list;
        for (int i = copy.length - 1; i >= 0; i--) {
            listT = cons(copy[i], listT);
        }
        return listT;
    }

    @Override
    public boolean exists(@Nonnull F f) {
        NonEmptyImmutableList list = this;
        while (true) {
            if (f.apply(list.head)) {
                return true;
            }
            if (list.tail instanceof Nil) {
                return false;
            }
            list = ((NonEmptyImmutableList) list.tail);
        }
    }

    @Override
    public boolean every(@Nonnull F f) {
        NonEmptyImmutableList list = this;
        while (true) {
            if (!f.apply(list.head)) {
                return false;
            }
            if (list.tail instanceof Nil) {
                return true;
            }
            list = ((NonEmptyImmutableList) list.tail);
        }
    }

    @Override
    public boolean contains(@Nonnull T a) {
        NonEmptyImmutableList list = this;
        while (true) {
            if (list.head == a) {
                return true;
            }
            if (list.tail instanceof Nil) {
                return false;
            }
            list = ((NonEmptyImmutableList) list.tail);
        }
    }

    @Nonnull
    @Override
    public Pair, ImmutableList> span(@Nonnull F f) {
        @SuppressWarnings("unchecked")
        T[] result = (T[]) new Object[this.length];
        ImmutableList list = this;
        int j = 0;
        for (int i = 0; i < this.length; i++) {
            T el = ((NonEmptyImmutableList) list).head;
            if (f.apply(el)) {
                result[j] = el;
                j++;
            } else {
                break;
            }
            list = ((NonEmptyImmutableList) list).tail;
        }
        return new Pair<>(fromBounded(result, 0, j), list);
    }

    @Nonnull
    @Override
    public  ImmutableList flatMap(@Nonnull F> f) {
        ArrayList result = new ArrayList<>();
        ImmutableList list = this;
        while (list instanceof NonEmptyImmutableList) {
            ImmutableList bucket = f.apply(((NonEmptyImmutableList) list).head);
            while (bucket instanceof NonEmptyImmutableList) {
                result.add(((NonEmptyImmutableList) bucket).head);
                bucket = ((NonEmptyImmutableList) bucket).tail;
            }
            list = ((NonEmptyImmutableList) list).tail;
        }
        return from(result);
    }

    @Nonnull
    @Override
    public ImmutableList removeAll(@Nonnull F f) {
        return this.filter(x -> !f.apply(x));
    }

    @Nonnull
    @Override
    public NonEmptyImmutableList reverse() {
        ImmutableList list = this;
        ImmutableList acc = empty();
        while (list instanceof NonEmptyImmutableList) {
            acc = acc.cons(((NonEmptyImmutableList) list).head);
            list = ((NonEmptyImmutableList) list).tail;
        }
        return (NonEmptyImmutableList) acc;
    }

    @Nonnull
    @Override
    public  Pair> mapAccumL(@Nonnull F2> f, @Nonnull B acc) {
        @SuppressWarnings("unchecked")
        C[] result = (C[]) new Object[this.length];
        ImmutableList list = this;
        for (int i = 0; i < this.length; i++) {
            Pair pair = f.apply(acc, ((NonEmptyImmutableList) list).head);
            acc = pair.left;
            result[i] = pair.right;
            list = ((NonEmptyImmutableList) list).tail;
        }
        return new Pair<>(acc, from(result));
    }

    @Nonnull
    @Override
    public ImmutableSet uniqByEquality() {
        return ImmutableSet.emptyUsingEquality().putAll(this);
    }

    @Nonnull
    @Override
    public ImmutableSet uniqByIdentity() {
        return ImmutableSet.emptyUsingIdentity().putAll(this);
    }

    @Nonnull
    @Override
    public  ImmutableSet uniqByEqualityOn(@Nonnull F f) {
        ImmutableSet set = ImmutableSet.emptyUsingEquality().put(f.apply(this.head));
        ImmutableSet out = ImmutableSet.emptyUsingIdentity().put(this.head);
        ImmutableList list = this.tail;
        for (int i = 1; i < this.length; i++) {
            T a = ((NonEmptyImmutableList) list).head;
            B b = f.apply(a);
            if (!set.contains(b)) {
                out = out.put(a);
            }
            set = set.put(b);
            list = ((NonEmptyImmutableList) list).tail;
        }
        return out;
    }

    @Override
    protected int calcHashCode() {
        NonEmptyImmutableList list = this;
        int[] hashStack = new int[list.length];
        ImmutableList[] sublistStack = (ImmutableList[]) new ImmutableList[list.length];
        int stackIndex = 0;
        int hash = HASH_START;
        while (true) {
            Integer cachedHashCode = list.getCachedHashCode();
            if (cachedHashCode != null) {
                hash = cachedHashCode;
                break;
            }
            hashStack[stackIndex] = list.head.hashCode();
            sublistStack[stackIndex] = list;
            ++stackIndex;
            if (list.tail instanceof NonEmptyImmutableList) {
                list = (NonEmptyImmutableList) list.tail;
            } else {
                break;
            }
        }
        --stackIndex;

        for (; stackIndex >= 0; --stackIndex) {
            hash = HashCodeBuilder.put(hash, hashStack[stackIndex]);
            sublistStack[stackIndex].setCachedHashCode(hash);
        }

        return hash;
    }
}