package org.infinispan.objectfilter.impl.aggregation; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; /** * Groups rows by their grouping fields and computes aggregates. * * @author anistor@redhat.com * @since 8.0 */ public final class Grouper { /** * The number of columns at the beginning of the row that are used for grouping. */ private final int noOfGroupingColumns; private final FieldAccumulator[] accumulators; private final boolean twoPhaseAcc; private final int inRowLength; private final int outRowLength; /** * Store the a row for each group. This is used only if we have at least one grouping column. */ private final LinkedHashMap<GroupRowKey, Object[]> groups; /** * The single row used for computing global aggregations (the are no grouping columns defined). */ private final Object[] globalGroup; private final class GroupRowKey { private final Object[] row; GroupRowKey(Object[] row) { this.row = row; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GroupRowKey other = (GroupRowKey) o; int n = noOfGroupingColumns > 0 ? noOfGroupingColumns : row.length; for (int i = 0; i < n; i++) { Object o1 = row[i]; Object o2 = other.row[i]; if (!(o1 == null ? o2 == null : o1.equals(o2))) { return false; } } return true; } @Override public int hashCode() { int result = 1; int n = noOfGroupingColumns > 0 ? noOfGroupingColumns : row.length; for (int i = 0; i < n; i++) { Object e = row[i]; result = 31 * result + (e == null ? 0 : e.hashCode()); } return result; } } /** * noOfGroupingColumns and accumulators must not have overlapping indices. */ public Grouper(int noOfGroupingColumns, FieldAccumulator[] accumulators, boolean twoPhaseAcc) { this.noOfGroupingColumns = noOfGroupingColumns; this.accumulators = accumulators != null && accumulators.length != 0 ? accumulators : null; this.twoPhaseAcc = twoPhaseAcc; inRowLength = findInRowLength(noOfGroupingColumns, accumulators); if (inRowLength == 0) { throw new IllegalArgumentException("Must have at least one grouping or aggregated column"); } outRowLength = noOfGroupingColumns + (accumulators != null ? accumulators.length : 0); if (noOfGroupingColumns > 0) { groups = new LinkedHashMap<GroupRowKey, Object[]>(); globalGroup = null; } else { groups = null; // we have global aggregations only globalGroup = new Object[outRowLength]; for (FieldAccumulator acc : accumulators) { acc.init(globalGroup); } } } private int findInRowLength(int noOfGroupingColumns, FieldAccumulator[] accumulators) { int l = noOfGroupingColumns; if (accumulators != null) { for (FieldAccumulator acc : accumulators) { if (acc.inPos + 1 > l) { l = acc.inPos + 1; } } } return l; } public void addRow(Object[] row) { if (row.length != inRowLength) { throw new IllegalArgumentException("Row length mismatch"); } if (noOfGroupingColumns > 0) { // compute grouping and aggregations GroupRowKey groupRowKey = new GroupRowKey(row); Object[] existingGroup = groups.get(groupRowKey); if (existingGroup == null) { existingGroup = new Object[outRowLength]; System.arraycopy(row, 0, existingGroup, 0, noOfGroupingColumns); if (accumulators != null) { FieldAccumulator.init(existingGroup, accumulators); for (FieldAccumulator acc : accumulators) { acc.init(existingGroup); } } groups.put(groupRowKey, existingGroup); } if (accumulators != null) { if (twoPhaseAcc) { FieldAccumulator.merge(row, existingGroup, accumulators); } else { FieldAccumulator.update(row, existingGroup, accumulators); } } } else { // we have global aggregations only if (twoPhaseAcc) { FieldAccumulator.merge(row, globalGroup, accumulators); } else { FieldAccumulator.update(row, globalGroup, accumulators); } } } public Iterator<Object[]> finish() { if (groups != null) { return new Iterator<Object[]>() { private final Iterator<Object[]> iterator = groups.values().iterator(); @Override public boolean hasNext() { return iterator.hasNext(); } @Override public Object[] next() { Object[] row = iterator.next(); if (accumulators != null) { FieldAccumulator.finish(row, accumulators); } return row; } }; } else { FieldAccumulator.finish(globalGroup, accumulators); return Collections.singleton(globalGroup).iterator(); } } @Override public String toString() { return "Grouper{" + "noOfGroupingColumns=" + noOfGroupingColumns + ", inRowLength=" + inRowLength + ", outRowLength=" + outRowLength + ", accumulators=" + Arrays.toString(accumulators) + ", number of groups=" + groups.size() + '}'; } }