@Override
  @SuppressWarnings("unchecked")
  public String isValid() {

    String superIsValid = super.isValid();
    if (superIsValid != null) return superIsValid;

    if (intervals.isEmpty()) {
      return "No intervals specified";
    }

    for (int i = 1; i < intervals.size(); i++) {
      Interval<T> interval1 = intervals.get(i - 1);
      Interval<T> interval2 = intervals.get(i);

      if (!interval1.getMax().equals(interval2.getMin())) {
        return "Gap between " + interval1 + " and " + interval2;
      }

      if (interval1.getMin().equals(interval2.getMin())
          && interval1.getMax().equals(interval2.getMax())) {
        return "Repeating intervals " + interval1 + " and " + interval2;
      }
    }

    DataTypeWithRatioScale<T> type = (DataTypeWithRatioScale<T>) getDataType();
    if (lowerRange.getRepeatBound() != null
        && upperRange.getRepeatBound() != null
        && type.compare(lowerRange.getRepeatBound(), upperRange.getRepeatBound()) > 0) {
      return "Lower repeat bound must be < upper repeat bound";
    }

    if (lowerRange.getSnapBound() != null
        && lowerRange.getRepeatBound() != null
        && type.compare(lowerRange.getSnapBound(), lowerRange.getRepeatBound()) > 0) {
      return "Lower snap bound must be <= lower repeat bound";
    }

    if (lowerRange.getLabelBound() != null
        && lowerRange.getSnapBound() != null
        && type.compare(lowerRange.getLabelBound(), lowerRange.getSnapBound()) > 0) {
      return "Lower label bound must be <= lower snap bound";
    }

    if (upperRange.getRepeatBound() != null
        && upperRange.getSnapBound() != null
        && type.compare(upperRange.getSnapBound(), upperRange.getRepeatBound()) < 0) {
      return "Upper snap bound must be >= upper repeat bound";
    }

    if (lowerRange.getLabelBound() != null
        && upperRange.getSnapBound() != null
        && type.compare(upperRange.getLabelBound(), upperRange.getSnapBound()) < 0) {
      return "Upper label bound must be >= upper snap bound";
    }

    return null;
  }
  /**
   * Returns the matching interval.
   *
   * @param index
   * @param type
   * @param tValue
   * @return
   */
  @SuppressWarnings("unchecked")
  private Interval<T> getInterval(IndexNode index, DataTypeWithRatioScale<T> type, T tValue) {

    // Find interval
    int shift =
        (int)
            Math.floor(
                type.ratio(type.subtract(tValue, index.min), type.subtract(index.max, index.min)));
    T offset = type.multiply(type.subtract(index.max, index.min), shift);
    Interval<T> interval = getInterval(index, type.subtract(tValue, offset));

    // Check
    if (interval == null) {
      throw new IllegalStateException("No interval found for: " + type.format(tValue));
    }

    // Create first result interval
    T lower = type.add(interval.min, offset);
    T upper = type.add(interval.max, offset);
    return new Interval<T>(this, (DataType<T>) type, lower, upper, interval.function);
  }
 /**
  * Performs the index lookup.
  *
  * @param node
  * @param value
  * @return
  */
 private Interval<T> getInterval(IndexNode node, T value) {
   @SuppressWarnings("unchecked")
   DataTypeWithRatioScale<T> type = (DataTypeWithRatioScale<T>) getDataType();
   if (node.isLeaf) {
     for (Interval<T> leaf : node.leafs) {
       if (type.compare(leaf.min, value) <= 0 && type.compare(leaf.max, value) > 0) {
         return leaf;
       }
     }
   } else {
     for (IndexNode child : node.children) {
       if (type.compare(child.min, value) <= 0 && type.compare(child.max, value) > 0) {
         return getInterval(child, value);
       }
     }
   }
   throw new IllegalStateException("No interval found for: " + type.format(value));
 }
Example #4
0
  /**
   * Used for building summary statistics
   *
   * @param type
   * @param value
   * @param isPeriod Defines whether the parameter is a time period
   * @param isSquare Defines whether the period is a squared period
   * @return
   */
  @SuppressWarnings({"unchecked", "rawtypes"})
  private String toString(DataType<?> type, double value, boolean isPeriod, boolean isSquare) {

    // Handle corner cases
    if (Double.isNaN(value)) {
      return "Not available";
    } else if (Double.isInfinite(value)) {
      if (value < 0) {
        return "-Infinity";
      } else {
        return "+Infinity";
      }
    }

    // Handle periods
    if (isPeriod) {

      // Init
      long SECONDS = 1000;
      long MINUTES = 60 * SECONDS;
      long HOURS = 60 * MINUTES;
      long DAYS = 24 * HOURS;
      long WEEKS = 7 * DAYS;

      // Square
      if (isSquare) {
        SECONDS *= SECONDS;
        MINUTES *= MINUTES;
        HOURS *= HOURS;
        DAYS *= DAYS;
        WEEKS *= WEEKS;
      }

      // Compute
      final int weeks = (int) (value / WEEKS);
      value = value % WEEKS;
      final int days = (int) (value / DAYS);
      value = value % DAYS;
      final int hours = (int) (value / HOURS);
      value = value % HOURS;
      final int minutes = (int) (value / MINUTES);
      value = value % MINUTES;
      final int seconds = (int) (value / SECONDS);
      value = value % SECONDS;
      final int milliseconds = (int) (value);

      // Convert
      StringBuilder builder = new StringBuilder();
      if (weeks != 0) builder.append(weeks).append(isSquare ? "w^2, " : "w, ");
      if (days != 0) builder.append(days).append(isSquare ? "d^2, " : "d, ");
      if (hours != 0) builder.append(hours).append(isSquare ? "h^2, " : "h, ");
      if (minutes != 0) builder.append(minutes).append(isSquare ? "m^2, " : "m, ");
      if (seconds != 0) builder.append(seconds).append(isSquare ? "s^2, " : "s, ");
      builder.append(milliseconds).append(isSquare ? "ms^2" : "ms");

      // Return
      return builder.toString();
    }

    // Handle data types
    if (type instanceof DataTypeWithRatioScale) {
      DataTypeWithRatioScale rType = (DataTypeWithRatioScale) type;
      return rType.format(rType.fromDouble(value));
    } else {
      return String.valueOf(value);
    }
  }
  @Override
  @SuppressWarnings("unchecked")
  protected AbstractGroup[][] prepareGroups() {

    // Check
    String valid = isValid();
    if (valid != null) {
      throw new IllegalArgumentException(valid);
    }

    // Create adjustments
    Range<T>[] ranges = getAdjustedRanges();
    Range<T> tempLower = ranges[0];
    Range<T> tempUpper = ranges[1];

    // Build leaf level index
    ArrayList<IndexNode> nodes = new ArrayList<IndexNode>();
    for (int i = 0, len = intervals.size(); i < len; i += INDEX_FANOUT) {
      int min = i;
      int max = Math.min(i + INDEX_FANOUT - 1, len - 1);

      List<Interval<T>> leafs = new ArrayList<Interval<T>>();
      for (int j = min; j <= max; j++) {
        leafs.add(intervals.get(j));
      }

      nodes.add(
          new IndexNode(
              intervals.get(min).min,
              intervals.get(max).max,
              leafs.toArray(new Interval[leafs.size()])));
    }

    // Builder inner nodes
    while (nodes.size() > 1) {
      List<IndexNode> current = (List<IndexNode>) nodes.clone();
      nodes.clear();
      for (int i = 0, len = current.size(); i < len; i += INDEX_FANOUT) {
        int min = i;
        int max = Math.min(i + INDEX_FANOUT - 1, len - 1);
        List<IndexNode> temp = new ArrayList<IndexNode>();
        for (int j = min; j <= max; j++) {
          temp.add(current.get(j));
        }

        nodes.add(
            new IndexNode(
                current.get(min).min,
                current.get(max).max,
                temp.toArray(new HierarchyBuilderIntervalBased.IndexNode[temp.size()])));
      }
    }

    // Prepare
    String[] data = getData();
    List<AbstractGroup[]> result = new ArrayList<AbstractGroup[]>();
    IndexNode index = nodes.get(0);

    // Prepare
    DataTypeWithRatioScale<T> type = (DataTypeWithRatioScale<T>) getDataType();
    Map<AbstractGroup, AbstractGroup> cache = new HashMap<AbstractGroup, AbstractGroup>();

    // Create snap intervals
    Interval<T> lowerSnap = getInterval(index, type, tempLower.repeatBound);
    lowerSnap =
        new Interval<T>(
            this, getDataType(), tempLower.snapBound, lowerSnap.max, lowerSnap.function);

    Interval<T> upperSnap = getIntervalUpperSnap(index, type, tempUpper.repeatBound);
    upperSnap =
        new Interval<T>(
            this, getDataType(), upperSnap.min, tempUpper.snapBound, upperSnap.function);

    // Overlapping snaps -> one interval
    if (type.compare(lowerSnap.max, upperSnap.min) > 0) {
      // We could use lowerSnap.function or upperSnap.function
      lowerSnap =
          new Interval<T>(this, getDataType(), lowerSnap.min, upperSnap.max, lowerSnap.function);
      upperSnap = lowerSnap;
    }

    // Create first column
    AbstractGroup[] first = new AbstractGroup[data.length];
    for (int i = 0; i < data.length; i++) {
      T value = type.parse(data[i]);
      Interval<T> interval;

      if (value == null) {
        interval = new Interval<T>(this);
      } else if (type.compare(value, tempLower.labelBound) < 0) {
        throw new IllegalArgumentException(type.format(value) + " is < lower label bound");
      } else if (type.compare(value, tempLower.snapBound) < 0) {
        interval = new Interval<T>(this, true, tempLower.snapBound);
      } else if (type.compare(value, tempUpper.labelBound) >= 0) {
        throw new IllegalArgumentException(type.format(value) + " is >= upper label bound");
      } else if (type.compare(value, tempUpper.snapBound) >= 0) {
        interval = new Interval<T>(this, false, tempUpper.snapBound);
      } else {
        interval = getInterval(index, type, value);
      }

      if (interval.min != null && interval.max != null) {
        if (type.compare(interval.min, lowerSnap.max) < 0) {
          interval = lowerSnap;
        } else if (type.compare(interval.max, upperSnap.min) > 0) {
          interval = upperSnap;
        }
      }

      first[i] = getGroup(cache, interval);
    }
    result.add(first);

    // Clean
    index = null;

    // Create other columns
    List<Group<T>> groups = new ArrayList<Group<T>>();
    if (!super.getLevels().isEmpty()) groups = super.getLevels().get(0).getGroups();
    if (cache.size() > 1 && !groups.isEmpty()) {

      // Prepare
      List<Interval<T>> newIntervals = new ArrayList<Interval<T>>();
      int intervalIndex = 0;
      int multiplier = 0;
      T width = type.subtract(intervals.get(intervals.size() - 1).max, intervals.get(0).min);

      // Merge intervals
      for (Group<T> group : groups) {

        // Find min and max
        T min = null;
        T max = null;
        for (int i = 0; i < group.getSize(); i++) {
          Interval<T> current = intervals.get(intervalIndex++);
          T offset = type.multiply(width, multiplier);
          T cMin = type.add(current.min, offset);
          T cMax = type.add(current.max, offset);
          if (min == null || type.compare(min, cMin) > 0) {
            min = cMin;
          }
          if (max == null || type.compare(max, cMax) < 0) {
            max = cMax;
          }
          if (intervalIndex == intervals.size()) {
            intervalIndex = 0;
            multiplier++;
          }
        }

        // Add interval
        newIntervals.add(new Interval<T>(this, getDataType(), min, max, group.getFunction()));
      }

      // Compute next column
      HierarchyBuilderIntervalBased<T> builder =
          new HierarchyBuilderIntervalBased<T>(getDataType(), tempLower, tempUpper);
      for (Interval<T> interval : newIntervals) {
        builder.addInterval(interval.min, interval.max, interval.function);
      }

      for (int i = 1; i < super.getLevels().size(); i++) {
        for (Group<T> sgroup : super.getLevel(i).getGroups()) {
          builder.getLevel(i - 1).addGroup(sgroup.getSize(), sgroup.getFunction());
        }
      }

      // Copy data
      builder.prepare(data);
      AbstractGroup[][] columns = builder.getPreparedGroups();
      for (AbstractGroup[] column : columns) {
        result.add(column);
      }
    } else {
      if (cache.size() > 1) {
        AbstractGroup[] column = new AbstractGroup[data.length];
        @SuppressWarnings("serial")
        AbstractGroup element = new AbstractGroup(DataType.ANY_VALUE) {};
        for (int i = 0; i < column.length; i++) {
          column[i] = element;
        }
        result.add(column);
      }
    }

    // Return
    return result.toArray(new AbstractGroup[0][0]);
  }
  /**
   * Returns the matching interval.
   *
   * @param index
   * @param type
   * @param tValue
   * @return
   */
  @SuppressWarnings("unchecked")
  private Interval<T> getIntervalUpperSnap(
      IndexNode index, DataTypeWithRatioScale<T> type, T tValue) {

    // Find interval
    double shift =
        Math.floor(
            type.ratio(type.subtract(tValue, index.min), type.subtract(index.max, index.min)));
    T offset = type.multiply(type.subtract(index.max, index.min), shift);
    T value = type.subtract(tValue, offset);
    Interval<T> interval = null;

    for (int j = 0; j < intervals.size(); j++) {
      Interval<T> i = intervals.get(j);
      if (type.compare(i.min, value) <= 0 && type.compare(i.max, value) > 0) {

        // If on lower bound, use next-lower interval
        if (type.compare(value, i.min) == 0) {
          if (j > 0) {

            // Simply use the next one
            interval = intervals.get(j - 1);
            break;
          } else {

            // Wrap around
            interval = intervals.get(intervals.size() - 1);
            offset = type.multiply(type.subtract(index.max, index.min), shift - 1);
            break;
          }
        } else {
          interval = i;
          break;
        }
      }
    }

    if (interval == null && intervals.size() == 1) {
      interval = intervals.get(0);
    }

    // Check
    if (interval == null) {
      throw new IllegalStateException("Internal error. Sorry for that!");
    }

    // Create first result interval
    T lower = type.add(interval.min, offset);
    T upper = type.add(interval.max, offset);
    return new Interval<T>(this, (DataType<T>) type, lower, upper, interval.function);
  }