static <E> Subgroup<E> smallSubgroup(final Group<E> group, final Group<E> subgroup) { if (group == null) throw new IllegalArgumentException("null group"); if (subgroup == null) throw new IllegalArgumentException("null subgroup"); if (!group.getSize().isSmall()) throw new IllegalArgumentException("large group not supported"); if (!group.equality().equals(subgroup.equality())) throw new IllegalArgumentException("equality differs between group and subgroup"); E id1 = group.op().identity(); E id2 = subgroup.op().identity(); if (!group.equality().isEquivalent(id1, id2)) throw new IllegalArgumentException("identity differs between group and subgroup"); return new SmallSubgroup<E>(group, subgroup); }
@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]); }