/**
* AnalyzerBeans
* Copyright (C) 2014 Neopost - Customer Information Management
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.eobjects.analyzer.result.renderer;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.eobjects.analyzer.result.Crosstab;
import org.eobjects.analyzer.result.CrosstabDimension;
import org.eobjects.analyzer.result.CrosstabNavigator;
import org.eobjects.analyzer.result.ResultProducer;
import org.apache.metamodel.util.CollectionUtils;
/**
* A class that encapsulates all the complicated logic of traversing a crosstab
* in the correct order, if it is to be rendered using two axes (horizontal and
* vertical). The actual output of the rendering is not specified by this class
* - it uses a callback class for all the concrete rendering.
*
* @see CrosstabRendererCallback
*
*
*/
public class CrosstabRenderer {
private static final int MAX_HORIZONTAL_CELLS = 10;
private final Crosstab<?> crosstab;
private final List<CrosstabDimension> horizontalDimensions;
private final List<CrosstabDimension> verticalDimensions;
private int horizontalCells = 1;
private int verticalCells = 1;
public CrosstabRenderer(Crosstab<?> crosstab) {
if (crosstab == null) {
throw new IllegalArgumentException("Crosstab cannot be null");
}
this.crosstab = crosstab;
this.horizontalDimensions = new ArrayList<CrosstabDimension>();
this.verticalDimensions = new ArrayList<CrosstabDimension>();
}
public void autoAssignDimensions() {
// create a list of dimensions (with max 10 categories) to be layed
// out horizontally
List<CrosstabDimension> dimensions = crosstab.getDimensions();
List<CrosstabDimension> autoAssignDimensions = new LinkedList<CrosstabDimension>();
for (CrosstabDimension dimension : dimensions) {
// boolean hasCategories = dimension.getCategoryCount() > 0;
// if (hasCategories && !isAssigned(dimension)) {
if (!isAssigned(dimension)) {
autoAssignDimensions.add(dimension);
}
}
if (autoAssignDimensions.size() == 2) {
makeHorizontal(autoAssignDimensions.get(0));
makeVertical(autoAssignDimensions.get(1));
} else {
for (CrosstabDimension dimension : autoAssignDimensions) {
boolean horizontal = false;
int categoryCount = dimension.getCategoryCount();
if (horizontalCells <= MAX_HORIZONTAL_CELLS && categoryCount <= MAX_HORIZONTAL_CELLS) {
if (horizontalCells * categoryCount <= MAX_HORIZONTAL_CELLS) {
makeHorizontal(dimension);
horizontal = true;
}
}
if (!horizontal) {
makeVertical(dimension);
}
}
}
}
public boolean isAssigned(CrosstabDimension dimension) {
return verticalDimensions.contains(dimension) || horizontalDimensions.contains(dimension);
}
public void makeHorizontal(CrosstabDimension dimension) {
if (verticalDimensions.contains(dimension)) {
verticalDimensions.remove(dimension);
verticalCells = verticalCells / dimension.getCategoryCount();
}
if (!horizontalDimensions.contains(dimension)) {
horizontalDimensions.add(dimension);
horizontalCells = horizontalCells * dimension.getCategoryCount();
}
}
public void makeVertical(CrosstabDimension dimension) {
if (horizontalDimensions.contains(dimension)) {
horizontalDimensions.remove(dimension);
horizontalCells = horizontalCells / dimension.getCategoryCount();
}
if (!verticalDimensions.contains(dimension)) {
verticalDimensions.add(dimension);
verticalCells = verticalCells * dimension.getCategoryCount();
}
}
public <E> E render(CrosstabRendererCallback<E> callback) {
autoAssignDimensions();
List<CrosstabDimension> dimensions = crosstab.getDimensions();
if (CollectionUtils.isNullOrEmpty(dimensions)) {
return callback.getResult();
}
if (CollectionUtils.isNullOrEmpty(horizontalDimensions) && CollectionUtils.isNullOrEmpty(verticalDimensions)) {
return callback.getResult();
}
callback.beginTable(crosstab, horizontalDimensions, verticalDimensions);
// print the (horizontal) headers
{
int colspan = horizontalCells;
int repeatHeaders = 1;
for (int i = 0; i < horizontalDimensions.size(); i++) {
CrosstabDimension dimension = horizontalDimensions.get(i);
if (dimension.getCategoryCount() > 0) {
callback.beginRow();
// empty cells for each vertical dimension
for (CrosstabDimension verticalDimension : verticalDimensions) {
callback.emptyHeader(verticalDimension, dimension);
}
colspan = colspan / dimension.getCategoryCount();
for (int j = 0; j < repeatHeaders; j++) {
for (String category : dimension.getCategories()) {
callback.horizontalHeaderCell(category, dimension, colspan);
}
}
repeatHeaders = repeatHeaders * dimension.getCategoryCount();
callback.endRow();
}
}
}
// print the content rows
{
CrosstabNavigator<?> navigator = crosstab.navigate();
for (int i = 0; i < verticalCells; i++) {
callback.beginRow();
navigateOnAxis(verticalDimensions, i, verticalCells, navigator);
// print the vertical headers
{
int rowspan = verticalCells;
for (int j = 0; j < verticalDimensions.size(); j++) {
CrosstabDimension dimension = verticalDimensions.get(j);
rowspan = rowspan / dimension.getCategoryCount();
if (i % rowspan == 0) {
String category = navigator.getCategory(dimension);
callback.verticalHeaderCell(category, dimension, rowspan);
}
}
}
for (int j = 0; j < horizontalCells; j++) {
navigateOnAxis(horizontalDimensions, j, horizontalCells, navigator);
final Object value = navigator.get();
final ResultProducer resultProducer = navigator.explore();
callback.valueCell(value, resultProducer);
}
callback.endRow();
}
}
callback.endTable();
return callback.getResult();
}
private void navigateOnAxis(List<CrosstabDimension> dimensionsOnAxis, int cellIndex, int cellCount,
CrosstabNavigator<?> navigator) {
int colspan = cellCount;
int category = 0;
int localIndex = cellIndex;
for (int k = 0; k < dimensionsOnAxis.size(); k++) {
CrosstabDimension dimension = dimensionsOnAxis.get(k);
int categoryCount = dimension.getCategoryCount();
int offset = category * colspan;
colspan = colspan / categoryCount;
localIndex = localIndex - offset;
category = localIndex / colspan;
String categoryName = dimension.getCategories().get(category);
navigator.where(dimension, categoryName);
}
}
}