001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package gololang;
012
013import java.util.Collection;
014import java.util.Iterator;
015import java.util.Arrays;
016
017import org.eclipse.golo.runtime.InvalidDestructuringException;
018
019/**
020 * Represents a generic value range.
021 * <p>
022 * A range represent a set of values between to bounds, optionally with a step (or increment) from
023 * one value to the next.
024 */
025public interface Range<T> extends Collection<T>, HeadTail<T> {
026
027  /**
028   * Gets the lower bound of the range.
029   *
030   * @return the starting value of the range.
031   */
032  T from();
033
034  /**
035   * Gets the upper bound of the range.
036   *
037   * @return the excluded ending value of the range.
038   */
039  T to();
040
041  /**
042   * Gets the increment of the range.
043   *
044   * @return the number of value to between two elements in the range.
045   */
046  int increment();
047
048  /**
049   * Sets the increment of the range.
050   *
051   * @param value the new increment.
052   * @return the range itself.
053   */
054  Range<T> incrementBy(int value);
055
056  /**
057   * Sets the negative increment of the range.
058   *<p>
059   * this is equivalent to:
060   * <pre class="listing"><code class="lang-java" data-lang="java">
061   * range.incrementBy(-value)
062   * </code></pre>
063   *
064   * @param value the new increment.
065   * @return the range itself.
066   */
067  Range<T> decrementBy(int value);
068
069  /**
070   * Checks if the range encloses the value.
071   * <p>
072   * i.e. if {@code from() <= value} and {@code value < to()} (for positive increments, resp. for
073   * negative ones), regardless the increment value.
074   * <p>
075   * For instance a range between 0 and 5 with an increment of 2 encloses 1 but don't contains it.
076   *
077   * @param value the value to check.
078   */
079  boolean encloses(T value);
080
081  /**
082   * Creates a new reversed range from this range.
083   * <p>
084   * i.e. swaps the {@code from()} and {@code to()} values and sets the increment to
085   * {@code -increment()}.
086   */
087  Range<T> reversed();
088
089  /**
090   * New style destructuring helper.
091   *
092   * <p>If a remainer if included, it will be a new range with same step and end values, starting to the next available
093   * value.
094   *
095   * @param number number of variable that will be affected.
096   * @param substruct whether the destructuring is complete or should contains a sub structure.
097   * @param toSkip a boolean array indicating the elements to skip.
098   * @return an array containing the values to assign.
099   */
100  default Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) {
101    if (number < size() && !substruct) {
102      throw InvalidDestructuringException.notEnoughValues(number, size(), substruct);
103    }
104    if (number == size() && !substruct) {
105      return org.eclipse.golo.runtime.ArrayHelper.nullify(toArray(), toSkip);
106    }
107    if (number <= size() && substruct) {
108      Object[] d = new Object[number];
109      Iterator<T> it = this.iterator();
110      for (int i = 0; i < number - 1; i++) {
111        if (Boolean.valueOf(true).equals(toSkip[i])) {
112          it.next();
113        } else {
114          d[i] = it.next();
115        }
116      }
117      if (Boolean.valueOf(false).equals(toSkip[number - 1])) {
118        d[number - 1] = newStartingFrom(it.next());
119      }
120      return d;
121    }
122    if (number == size() + 1 && substruct) {
123      Object[] d = Arrays.copyOf(toArray(), number);
124      if (Boolean.valueOf(false).equals(toSkip[number - 1])) {
125        d[number - 1] = newStartingFrom(to());
126      }
127      return org.eclipse.golo.runtime.ArrayHelper.nullify(d, toSkip);
128    }
129    throw InvalidDestructuringException.tooManyValues(number);
130  }
131
132  /**
133   * Returns a copy of this range with a new starting value.
134   *
135   * <p>There is no check that the {@code newStart} value is compatible with the current start and increment. It is
136   * therefore possible that the new range yields different values than the original.
137   */
138  public Range<T> newStartingFrom(T newStart);
139}