dollar.api.types.DollarRange.java Source code

Java tutorial

Introduction

Here is the source code for dollar.api.types.DollarRange.java

Source

/*
 *    Copyright (c) 2014-2017 Neil Ellis
 *
 *    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 dollar.api.types;

import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import dollar.api.DollarException;
import dollar.api.DollarStatic;
import dollar.api.Type;
import dollar.api.Value;
import dollar.api.exceptions.DollarFailureException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;

import static dollar.api.DollarStatic.$void;
import static dollar.api.types.DollarFactory.INFINITY;
import static dollar.api.types.DollarFactory.wrap;

public class DollarRange extends AbstractDollar {

    @NotNull
    private final Range<Value> range;
    private boolean reversed;

    public DollarRange(@NotNull Value start, @NotNull Value finish) {
        super();
        Value startUnwrap;
        Value finishUnwrap;

        if (start.compareTo(finish) < 0) {
            startUnwrap = start.$unwrap();
            finishUnwrap = finish.$unwrap();
        } else {
            startUnwrap = finish.$unwrap();
            finishUnwrap = start.$unwrap();
            reversed = true;

        }

        assert startUnwrap != null;
        assert finishUnwrap != null;
        range = Range.closed(startUnwrap, finishUnwrap);
    }

    public DollarRange(boolean lowerBounds, boolean upperBounds, boolean closedLeft, boolean closedRight,
            @Nullable Value lower, @Nullable Value upper) {
        super();
        Value lowerBound;
        Value upperBound;
        if ((lower != null) && (upper != null) && (lower.compareTo(upper) > 0)) {
            lowerBound = upper;
            upperBound = lower;
            reversed = true;
        } else {
            lowerBound = lower;
            upperBound = upper;
        }
        if (!lowerBounds && !upperBounds) {
            range = Range.all();
        } else if (!lowerBounds) {
            if (closedRight) {
                range = Range.atMost(upperBound);
            } else {
                range = Range.lessThan(upperBound);
            }
        } else if (!upperBounds) {
            if (closedLeft) {
                range = Range.atLeast(lowerBound);
            } else {
                range = Range.greaterThan(lowerBound);
            }
        } else if (closedLeft) {
            if (closedRight) {
                range = Range.closed(lowerBound, upperBound);
            } else {
                //openRight
                range = Range.closedOpen(lowerBound, upperBound);
            }
        } else if (!closedLeft) {
            //openLeft
            if (closedRight) {
                range = Range.openClosed(lowerBound, upperBound);
            } else {
                //openRight
                if (lowerBound.equals(upperBound)) {
                    throw new IllegalArgumentException(
                            "Cannot have an open range with lower bounds being the same as upper " + "bounds");
                } else {
                    range = Range.open(lowerBound, upperBound);
                }
            }
        } else {
            throw new IllegalStateException();
        }
    }

    public DollarRange(@NotNull Range<Value> range, boolean reversed) {
        super();
        this.range = range;
        this.reversed = reversed;
    }

    @NotNull
    @Override
    public Value $abs() {
        return wrap(
                new DollarRange(
                        Range.range(DollarFactory.fromValue(range.lowerEndpoint().$abs()), range.lowerBoundType(),
                                DollarFactory.fromValue(range.upperEndpoint().$abs()), range.upperBoundType()),
                        reversed));
    }

    @NotNull
    @Override
    public Value $append(@NotNull Value value) {
        return $plus(value);
    }

    @NotNull
    @Override
    public Value $as(@NotNull Type type) {
        if (type.is(Type._LIST)) {
            return DollarStatic.$(toVarList());
        } else if (type.is(Type._MAP)) {
            return DollarStatic.$(toJavaMap());
        } else if (type.is(Type._STRING)) {
            return DollarFactory.fromStringValue(toHumanString());
        } else if (type.is(Type._VOID)) {
            return $void();
        } else {
            throw new DollarFailureException(ErrorType.INVALID_CAST);
        }
    }

    @NotNull
    @Override
    public Value $containsKey(@NotNull Value value) {
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public Value $containsValue(@NotNull Value value) {
        return DollarStatic.$(range.contains(value));
    }

    @NotNull
    @Override
    public Value $divide(@NotNull Value rhs) {
        Value rhsFix = rhs.$fixDeep();
        return wrap(new DollarRange(
                Range.range(DollarFactory.fromValue(range.lowerEndpoint().$divide(rhsFix)), range.lowerBoundType(),
                        DollarFactory.fromValue(range.upperEndpoint().$divide(rhsFix)), range.upperBoundType()),
                reversed));

    }

    @NotNull
    @Override
    public Value $get(@NotNull Value key) {
        if (key.integer()) {
            long keyL;
            if (key.toLong() < 0) {
                keyL = size() + key.toLong();
            } else {
                keyL = key.toLong();
            }
            Value upper = range.upperEndpoint();
            assert upper != null;
            Value lower = range.lowerEndpoint();
            assert lower != null;

            // (1..3) 2,2 [1..3] 1,3
            if (reversed && !range.hasUpperBound()) {
                throw new DollarException(
                        "Attempted to get an element from an unbounded range offset from the upper bound "
                                + "(reversed)");
            }
            if (!reversed && !range.hasLowerBound()) {
                throw new DollarException(
                        "Attempted to get an element from an unbounded range offset from the lower bound "
                                + "(not reversed)");
            }
            if (range.upperBoundType().equals(BoundType.OPEN)) {
                upper = upper.$dec();
            }

            if (range.lowerBoundType().equals(BoundType.OPEN)) {
                lower = lower.$inc();
            }
            if (upper.compareTo(lower) < 0) {
                throw new DollarException("Elements not available in an empty range");
            }
            if (upper.integer()) {
                if ((upper.toLong() == lower.toLong()) && (keyL == 0)) {
                    return DollarFactory.fromValue(lower);
                }
                final long result = reversed ? (upper.toLong() - keyL) : (lower.toLong() + keyL);
                return DollarFactory.fromValue(result);
            }
            if (upper.decimal()) {
                if ((upper.toDouble() == lower.toDouble()) && (keyL == 0)) {
                    return DollarFactory.fromValue(lower);
                }
                final double diff = reversed ? (upper.toDouble() - keyL) : (lower.toDouble() + keyL);
                return DollarFactory.fromValue(diff + Math.signum(diff));
            }
            if (upper.equals(lower) && (keyL == 0)) {
                return DollarFactory.fromValue(lower);
            }
            if (!reversed) {
                Value start = lower;
                for (long i = 0; i < keyL; i++) {
                    start = start.$inc();
                }
                return start;
            } else {
                Value finish = upper;
                for (long i = 0; i < keyL; i++) {
                    finish = finish.$dec();
                }
                return finish;
            }

        }
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public Value $has(@NotNull Value key) {
        return DollarStatic.$(false);
    }

    @NotNull
    @Override
    public Value $insert(@NotNull Value value, int position) {
        return $get(DollarStatic.$(toVarList())).$insert(value, position);
    }

    @NotNull
    @Override
    public Value $minus(@NotNull Value rhs) {
        Value rhsFix = rhs.$fixDeep();
        return wrap(new DollarRange(
                Range.range(DollarFactory.fromValue(range.lowerEndpoint().$minus(rhsFix)), range.lowerBoundType(),
                        DollarFactory.fromValue(range.upperEndpoint().$minus(rhsFix)), range.upperBoundType()),
                reversed));
    }

    @NotNull
    @Override
    public Value $modulus(@NotNull Value rhs) {
        Value rhsFix = rhs.$fixDeep();
        return wrap(new DollarRange(
                Range.range(DollarFactory.fromValue(range.lowerEndpoint().$modulus(rhsFix)), range.lowerBoundType(),
                        DollarFactory.fromValue(range.upperEndpoint().$modulus(rhsFix)), range.upperBoundType()),
                reversed));

    }

    @NotNull
    @Override
    public Value $multiply(@NotNull Value rhs) {
        Value rhsFix = rhs.$fixDeep();
        return wrap(new DollarRange(Range.range(DollarFactory.fromValue(range.lowerEndpoint().$multiply(rhsFix)),
                range.lowerBoundType(), DollarFactory.fromValue(range.upperEndpoint().$multiply(rhsFix)),
                range.upperBoundType()), reversed));

    }

    @NotNull
    @Override
    public Value $negate() {
        return wrap(new DollarRange(
                Range.range(DollarFactory.fromValue(range.lowerEndpoint().$negate()), range.lowerBoundType(),
                        DollarFactory.fromValue(range.upperEndpoint().$negate()), range.upperBoundType()),
                reversed));
    }

    @NotNull
    @Override
    public Value $plus(@NotNull Value rhs) {
        Value rhsFix = rhs.$fixDeep();

        return wrap(new DollarRange(
                Range.range(DollarFactory.fromValue(range.lowerEndpoint().$plus(rhsFix)), range.lowerBoundType(),
                        DollarFactory.fromValue(range.upperEndpoint().$plus(rhsFix)), range.upperBoundType()),
                reversed));

    }

    @NotNull
    @Override
    public Value $prepend(@NotNull Value value) {
        return $get(DollarStatic.$(toVarList())).$prepend(value);
    }

    @NotNull
    @Override
    public Value $remove(@NotNull Value value) {
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public Value $removeByKey(@NotNull String value) {
        return $copy();

    }

    @NotNull
    @Override
    public Value $set(@NotNull Value key, @NotNull Object value) {
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public Value $size() {
        return diff().$abs();
    }

    @NotNull
    @Override
    public Type $type() {
        return new Type(Type._RANGE, constraintLabel());
    }

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

    @Override
    public boolean is(@NotNull Type... types) {
        for (Type type : types) {
            if (type.is(Type._RANGE)) {
                return true;
            }
        }
        return false;
    }

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

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

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

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

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

    @NotNull
    @Override
    public Value remove(@NotNull Object value) {
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public int size() {
        return Math.abs(diff().toInteger());
    }

    @NotNull
    @Override
    public String toDollarScript() {
        return "((" + lower().toDollarScript() + ")..(" + upper().toDollarScript() + "))";
    }

    @NotNull
    @Override
    public String toHumanString() {
        return String.format("%s..%s", lower(), upper());
    }

    @Override
    public int toInteger() {
        return diff().toInteger();
    }

    @NotNull
    @Override
    public <K extends Comparable<K>, V> ImmutableMap<K, V> toJavaMap() {
        return null;
    }

    @NotNull
    @Override
    public Range toJavaObject() {
        return range;
    }

    @NotNull
    @Override
    public ImmutableList<Object> toList() {
        List<Object> values = new ArrayList<>();
        Value start = lower();
        Value finish = upper();
        for (Value i = start; i.compareTo(finish) <= 0; i = i.$inc()) {
            values.add(i.toJavaObject());
        }
        return ImmutableList.copyOf(values);
    }

    @Override
    public ImmutableList<String> toStrings() {
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public ImmutableList<Value> toVarList() {
        List<Value> values = new ArrayList<>();
        Value start = lower();
        Value finish = upper();
        if (start.compareTo(finish) < 1) {
            for (Value i = start; i.compareTo(finish) <= 0; i = i.$inc()) {
                values.add(i);
            }
        } else {
            for (Value i = start; i.compareTo(finish) <= 0; i = i.$dec()) {
                values.add(i);
            }
        }
        return ImmutableList.copyOf(values);
    }

    @NotNull
    @Override
    public ImmutableMap<Value, Value> toVarMap() {
        @NotNull
        Value result;
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @NotNull
    @Override
    public String toYaml() {
        @NotNull
        Value result;
        throw new DollarFailureException(ErrorType.INVALID_RANGE_OPERATION);
    }

    @Override
    public boolean truthy() {
        return !range.isEmpty();
    }

    @NotNull
    @Override
    public Value $avg(boolean parallel) {
        //TODO: can we calculate without iteration?
        return $sum(parallel).$divide($size());
    }

    @NotNull
    @Override
    public Value $max(boolean parallel) {
        return upper();
    }

    @NotNull
    @Override
    public Value $min(boolean parallel) {
        return lower();
    }

    @NotNull
    @Override
    public Value $reverse(boolean parallel) {
        return wrap(new DollarRange(range, !reversed));
    }

    @NotNull
    @Override
    public Value $sort(boolean parallel) {
        return this;
    }

    @NotNull
    @Override
    public Value $unique(boolean parallel) {
        return this;
    }

    @Override
    public double toDouble() {
        return diff().toDouble();
    }

    @NotNull
    @Override
    public long toLong() {
        return diff().toLong();
    }

    @Override
    public int hashCode() {
        return range.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
        if (obj == null) {
            return false;
        }
        if (obj instanceof Value) {
            Value unwrapped = ((Value) obj).$fixDeep().$unwrap();
            if (unwrapped instanceof DollarRange) {
                return range.equals(((DollarRange) unwrapped).range);
            }
        }
        return false;

    }

    @Override
    public int compareTo(@NotNull Value o) {
        if ($containsValue(o).isTrue()) {
            return 0;
        }
        if (lower().compareTo(o) < 0) {
            return -1;
        }
        if (upper().compareTo(o) > 0) {
            return 1;
        }
        throw new IllegalStateException();
    }

    @NotNull
    private Value diff() {
        Value upper = range.upperEndpoint();
        assert upper != null;
        Value lower = range.lowerEndpoint();
        assert lower != null;

        // (1..3) 2,2 [1..3] 1,3
        if (!range.hasUpperBound()) {
            return INFINITY;
        }
        if (!range.hasLowerBound()) {
            return INFINITY;
        }
        if (range.upperBoundType().equals(BoundType.OPEN)) {
            upper = upper.$dec();
        }

        if (range.lowerBoundType().equals(BoundType.OPEN)) {
            lower = lower.$inc();
        }
        if (upper.compareTo(lower) < 0) {
            return $void();
        }
        if (upper.integer()) {
            if (upper.toLong() == lower.toLong()) {
                return DollarFactory.fromValue(1);
            }
            final long diff = reversed ? (lower.toLong() - upper.toLong()) : (upper.toLong() - lower.toLong());
            return DollarFactory.fromValue(diff + (long) Math.signum(diff));
        }
        if (upper.decimal()) {
            if (upper.toDouble() == lower.toDouble()) {
                return DollarFactory.fromValue(1.0);
            }
            final double diff = reversed ? (lower.toDouble() - upper.toDouble())
                    : (upper.toDouble() - lower.toDouble());
            return DollarFactory.fromValue(diff + Math.signum(diff));
        }
        int count = 0;
        Value start = lower;
        Value finish = upper;
        if (start.compareTo(finish) < 1) {
            for (Value i = start; i.compareTo(finish) <= 0; i = i.$inc()) {
                count++;
            }
        } else {
            for (Value i = start; i.compareTo(finish) <= 0; i = i.$dec()) {
                count--;
            }
        }
        return DollarFactory.fromValue(count);

    }

    @NotNull
    private Value lower() {
        if (reversed) {
            return range.upperEndpoint();
        } else {
            return range.lowerEndpoint();
        }
    }

    @NotNull
    private Value upper() {
        if (reversed) {
            return range.lowerEndpoint();
        } else {
            return range.upperEndpoint();
        }
    }

}