@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)); }
/** * 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); }