ro.fortsoft.wicket.pivot.DefaultPivotModel.java Source code

Java tutorial

Introduction

Here is the source code for ro.fortsoft.wicket.pivot.DefaultPivotModel.java

Source

/*
 * Copyright 2012 Decebal Suiu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in compliance with
 * the License. You may obtain a copy of the License in the LICENSE file, or at:
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package ro.fortsoft.wicket.pivot;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.map.MultiKeyMap;

import ro.fortsoft.wicket.pivot.FieldCalculation.FieldValueProvider;
import ro.fortsoft.wicket.pivot.tree.Node;
import ro.fortsoft.wicket.pivot.tree.Tree;
import ro.fortsoft.wicket.pivot.tree.TreeHelper;

/**
 * @author Decebal Suiu
 */
public class DefaultPivotModel implements PivotModel {

    private static final long serialVersionUID = 1L;

    private PivotDataSource dataSource;
    private List<PivotField> fields;
    private Tree columnsHeaderTree;
    private Tree rowsHeaderTree;
    private List<MultiKeyMap> calculatedData; // or use a MultiValueMap from apache commons

    private boolean showGrandTotalForColumn;
    private boolean showGrandTotalForRow;
    private boolean autoCalculate;

    public DefaultPivotModel(PivotDataSource dataSource) {
        this.dataSource = dataSource;

        // init fields
        int count = dataSource.getFieldCount();
        fields = new ArrayList<PivotField>(count);
        for (int i = 0; i < count; i++) {
            PivotField field = new PivotField(dataSource.getFieldName(i), i);
            field.setTitle(field.getName());
            field.setArea(PivotField.Area.UNUSED);
            field.setType(dataSource.getFieldType(i));
            fields.add(field);
        }
    }

    @Override
    public List<PivotField> getFields() {
        return fields;
    }

    @Override
    public PivotField getField(String name) {
        for (PivotField field : fields) {
            if (field.getName().equals(name)) {
                return field;
            }
        }

        return null;
    }

    @Override
    public PivotField getField(int index) {
        for (PivotField field : fields) {
            if (field.getIndex() == index) {
                return field;
            }
        }

        return null;
    }

    @Override
    public List<PivotField> getFields(PivotField.Area area) {
        List<PivotField> areaFields = new ArrayList<PivotField>();
        List<PivotField> fields = getFields();
        for (PivotField field : fields) {
            if (field.getArea().equals(area)) {
                areaFields.add(field);
            }
        }
        Collections.sort(areaFields);

        return areaFields;
    }

    @Override
    public PivotDataSource getDataSource() {
        return dataSource;
    }

    @Override
    public void calculate() {
        long start = System.currentTimeMillis();
        rowsHeaderTree = null;
        columnsHeaderTree = null;
        getRowsHeaderTree();
        long t1 = System.currentTimeMillis();
        System.out.println("created rowsHeaderTree in " + (t1 - start));
        getColumnsHeaderTree();
        long t2 = System.currentTimeMillis();
        System.out.println("created columnsHeaderTree in " + (t2 - t1));

        t1 = System.currentTimeMillis();
        List<PivotField> dataFields = getFields(PivotField.Area.DATA);
        calculatedData = new ArrayList<MultiKeyMap>();
        for (PivotField field : dataFields) {
            field.resetCalculation();
            calculatedData.add(getData(field));
        }
        t2 = System.currentTimeMillis();
        System.out.println("filled calculatedData in " + (t2 - t1));
        long stop = System.currentTimeMillis();
        System.out.println("calculated in " + (stop - start));
        System.out.println("calculatedData = " + calculatedData);
        // getValues(field, filter)
    }

    /*
     * TODO: trebuie imbunatatita metoda asta. Am facut un test pe un tabel
     * cu 4500 inregistrari si 7 coloane (nextreports downloads). Am observat ca
     * la 86 chei pe row si 212 chei pe column am 18.232 (86 x 212) combinatii.
     * Daca in getValues se sta 3,25 ms (cum am obtinut) rezulta un total de 
     * 5576 ms. Cred ca ar trebuii sa parcurg o singura data inregistrarile din baza.
     */
    private MultiKeyMap getData(PivotField dataField) {
        MultiKeyMap data = new MultiKeyMap();
        List<List<Object>> rowKeys = getRowKeys();
        System.out.println("rowKeys.size() = " + rowKeys.size());
        List<List<Object>> columnKeys = getColumnKeys();
        System.out.println("columnKeys.size() = " + columnKeys.size());

        List<PivotField> rowFields = getFields(PivotField.Area.ROW);
        List<PivotField> columnFields = getFields(PivotField.Area.COLUMN);
        for (List<Object> rowKey : rowKeys) {
            for (List<Object> columnKey : columnKeys) {
                Map<Integer, Object> rowFilter = getFilter(rowFields, rowKey);
                Map<Integer, Object> columnFilter = getFilter(columnFields, columnKey);
                final Map<Integer, Object> filter = new HashMap<Integer, Object>(rowFilter);
                filter.putAll(columnFilter);
                List<Object> values = getValues(dataField, filter);
                if (!CollectionUtils.isEmpty(values) || dataField.getFieldCalculation() != null) {
                    /*
                    System.out.println("filter = " + filter);
                    System.out.println("values = " + values);
                    System.out.println(values.size());
                    */
                    Object summary = PivotUtils.getSummary(dataField, values, new FieldValueProvider() {
                        @Override
                        public Object getFieldValue(PivotField field) {
                            List<Object> fieldValues = getValues(field, filter);
                            return field.getAggregator().init().addAll(fieldValues).getResult();
                        }
                    });
                    //               System.out.println("summary = " + summary);
                    data.put(rowKey, columnKey, summary);
                }
            }
        }

        return data;
    }

    @Override
    public Tree getColumnsHeaderTree() {
        if (columnsHeaderTree == null) {
            Node root = new Node();
            insertChildren(root, getFields(PivotField.Area.COLUMN));
            columnsHeaderTree = new Tree(root);
        }

        return columnsHeaderTree;
    }

    @Override
    public Tree getRowsHeaderTree() {
        if (rowsHeaderTree == null) {
            Node root = new Node();
            insertChildren(root, getFields(PivotField.Area.ROW));
            rowsHeaderTree = new Tree(root);
        }

        return rowsHeaderTree;
    }

    @Override
    public List<List<Object>> getRowKeys() {
        return TreeHelper.getLeafValues(getRowsHeaderTree().getRoot());
    }

    @Override
    public List<List<Object>> getColumnKeys() {
        return TreeHelper.getLeafValues(getColumnsHeaderTree().getRoot());
    }

    @Override
    public Object getValueAt(PivotField dataField, List<Object> rowKey, List<Object> columnKey) {
        int index = getFields(PivotField.Area.DATA).indexOf(dataField);
        return calculatedData.get(index).get(rowKey, columnKey);
    }

    @Override
    public boolean isShowGrandTotalForColumn() {
        return showGrandTotalForColumn;
    }

    @Override
    public void setShowGrandTotalForColumn(boolean showGrandTotalForColumn) {
        this.showGrandTotalForColumn = showGrandTotalForColumn;
    }

    @Override
    public boolean isShowGrandTotalForRow() {
        return showGrandTotalForRow;
    }

    @Override
    public void setShowGrandTotalForRow(boolean showGrandTotalForRow) {
        this.showGrandTotalForRow = showGrandTotalForRow;
    }

    @Override
    public boolean isAutoCalculate() {
        return autoCalculate;
    }

    @Override
    public void setAutoCalculate(boolean autoCalculate) {
        this.autoCalculate = autoCalculate;
    }

    @Override
    public String toString() {
        return "DefaultPivotModel [fields=" + fields + "]";
    }

    private void insertChildren(Node node, List<PivotField> fields) {
        // System.out.println("DefaultPivotModel.insertChildren()");
        Set<Object> values = getPossibleChildrenValues(node, fields);
        if (CollectionUtils.isEmpty(values)) {
            return;
        }

        Iterator<Object> it = values.iterator();
        while (it.hasNext()) {
            node.insert(it.next());
        }

        for (Node child : node.getChildren()) {
            insertChildren(child, fields);
        }
    }

    private Set<Object> getPossibleChildrenValues(Node node, List<PivotField> fields) {
        int level = node.getLevel();
        // System.out.println("level = " + level);
        // System.out.println("fields.size = " + fields.size());
        if (fields.size() <= level) {
            return null;
        }

        PivotField nextField = fields.get(level);
        // System.out.println("nextField = " + nextField);
        Map<Integer, Object> filter = getFilter(fields, node.getPathValues());
        // System.out.println("filter = " + filter);
        Set<Object> values = getUniqueValues(nextField, filter);
        // System.out.println("values = " + values);

        return values;
    }

    /*
     * Retrieves the values for a data field using a filter.
     */
    private List<Object> getValues(PivotField field, Map<Integer, Object> filter) {
        if (field.getFieldCalculation() != null)
            return Collections.emptyList();
        //      long start = System.currentTimeMillis();
        List<Object> values = new ArrayList<Object>();
        final int fieldIndex = field.getIndex();
        final int rowCount = dataSource.getRowCount();

        if (filter.isEmpty()) {
            /*
             * No filter -> Just add the values
             */
            for (int i = 0; i < rowCount; i++) {
                values.add(dataSource.getValueAt(i, fieldIndex));
            }
        } else {
            /*
             * Add all values matching the filter
             */
            for (int i = 0; i < rowCount; i++) {
                if (acceptValue(i, filter)) {
                    values.add(dataSource.getValueAt(i, fieldIndex));
                }
            }
        }
        //      long stop = System.currentTimeMillis();
        //      System.out.println("getValues in " + (stop - start));

        return values;
    }

    /*
     * Retrieves a filter for filtering data source (raw data). The size of fields must be equals with
     * the size of values. The key in map is the field index.  
     */
    private Map<Integer, Object> getFilter(List<PivotField> fields, List<Object> values) {
        //      long start = System.currentTimeMillis();
        Map<Integer, Object> filter = new HashMap<Integer, Object>();
        for (int i = 0; i < values.size(); i++) {
            int fieldIndex = fields.get(i).getIndex();
            // System.out.println(fieldIndex);
            filter.put(fieldIndex, values.get(i));
        }
        //      long stop = System.currentTimeMillis();
        //      System.out.println("getFilter in " + (stop - start));

        return filter;
    }

    private Set<Object> getUniqueValues(PivotField field, Map<Integer, Object> filter) {
        List<Object> values = getValues(field, filter);

        int sortOrder = field.getSortOrder();
        if (sortOrder != PivotField.SORT_ORDER_UNSORTED) {
            /*
             * We need to get the value set and sort it. We can not use a
             * TreeSet here as it does not allow null values.
             */
            Set<Object> valueSet = new HashSet<Object>(values);
            List<Object> valuesToOrder = new ArrayList<Object>(valueSet);
            final int sign = sortOrder == PivotField.SORT_ORDER_ASCENDING ? 1
                    : sortOrder == PivotField.SORT_ORDER_DESCENDING ? -1 : 1;
            Collections.sort(valuesToOrder, new Comparator<Object>() {
                @SuppressWarnings("unchecked")
                @Override
                public int compare(Object o1, Object o2) {
                    if (o1 == o2)
                        return 0;
                    if (o1 == null)
                        return sign * -1;
                    if (o2 == null)
                        return sign * 1;
                    return sign * ((Comparable<Object>) o1).compareTo(o2);
                }
            });

            return new LinkedHashSet<Object>(valuesToOrder);
        }

        return new LinkedHashSet<Object>(values);
    }

    private boolean acceptValue(int row, Map<Integer, Object> filter) {
        boolean accept = true;
        Set<Integer> keys = filter.keySet();
        Object value = null;
        for (int index : keys) {
            value = dataSource.getValueAt(row, fields.get(index));
            Object filterValue = filter.get(index);
            if (filterValue != value && (filterValue == null || !filterValue.equals(value))) {
                return false;
            }
        }

        return accept;
    }

}