Package org.openfaces.component.table

Source Code of org.openfaces.component.table.Summary

/*
* OpenFaces - JSF Component Library 2.0
* Copyright (C) 2007-2012, TeamDev Ltd.
* licensing@openfaces.org
* Unless agreed in writing the contents of this file are subject to
* the GNU Lesser General Public License Version 2.1 (the "LGPL" License).
* This library 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.
* Please visit http://openfaces.org/licensing/ for more details.
*/
package org.openfaces.component.table;

import org.openfaces.component.OUIOutput;
import org.openfaces.component.command.MenuItem;
import org.openfaces.component.command.PopupMenu;
import org.openfaces.component.table.impl.DynamicColumn;
import org.openfaces.component.table.impl.TableDataModel;
import org.openfaces.renderkit.TableUtil;
import org.openfaces.renderkit.select.SelectBooleanCheckboxImageManager;
import org.openfaces.util.ApplicationParams;
import org.openfaces.util.Components;
import org.openfaces.util.Rendering;
import org.openfaces.util.Resources;
import org.openfaces.util.ScriptBuilder;
import org.openfaces.util.ValueBindings;

import javax.el.ELContext;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Application;
import javax.faces.component.UIComponent;
import javax.faces.component.html.HtmlPanelGroup;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.NumberConverter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Summary extends OUIOutput {
    public static final String COMPONENT_TYPE = "org.openfaces.Summary";
    public static final String COMPONENT_FAMILY = "org.openfaces.Summary";

    private static final String DEFAULT_PATTERN = "#{function}: #{valueString}";
    private static final String ATTR_PATTERN = "pattern";
    private static final String FACET_POPUP_MENUS_CONTAINER = "_summariesPopupMenusContainer";
    private static final String ATTR_FUNCTION_NAME = "_functionName";

    private StampStates<StampState> stampStates = new StampStates<StampState>(this, StampState.class) {
        @Override
        protected String getStampContextId(FacesContext context) {
            Columns columns = getColumns();
            if (columns == null) return null;
            int columnIndex = columns.getColumnIndex();
            List<DynamicColumn> dynamicColumns = columns.toColumnList(context);
            DynamicColumn dynamicColumn = dynamicColumns.get(columnIndex);
            return dynamicColumn.getId();
        }

        @Override
        protected boolean isDefaultStateId(String stampContextId) {
            return stampContextId == null;
        }

        private Columns[] columnsReference;
        private Columns getColumns() {
            if (Summary.this.getParent() == null) {
                // invoking this method during attribute processing phase when the component is not embedded into view
                // yet shouldn't result in caching the null columns reference, which cannot be calculated at this stage
                return null;
            }
            if (columnsReference == null) {
                columnsReference = new Columns[]{Components.getParentWithClass(Summary.this, Columns.class)};
            }
            return columnsReference[0];
        }
    };

    private boolean implicitMode;

    public Summary() {
        this(false);
    }

    public Summary(boolean implicitMode) {
        setRendererType("org.openfaces.SummaryRenderer");
        this.implicitMode = implicitMode;
    }

    @Override
    public String getFamily() {
        return COMPONENT_FAMILY;
    }

    protected StampState stampState() {
        return stampStates.currentState(getFacesContext());
    }

    @Override
    public Object saveState(FacesContext context) {
        return new Object[]{
                super.saveState(context),
                stampStates.saveState(context),
                implicitMode
        };
    }

    @Override
    public void restoreState(FacesContext context, Object stateObj) {
        Object[] state = (Object[]) stateObj;
        int i = 0;
        super.restoreState(context, state[i++]);
        stampStates.restoreState(context, state[i++]);
        implicitMode = (Boolean) state[i++];
    }

    public static class StampState extends StampStates.State {
        private SummaryFunction function;
        private Boolean functionEditable;
        private UsageContext usageContext;

        private String originalClientId;

        private CalculationContext globalCalculationContext;
        private Map<RowGroup, CalculationContext> groupCalculationContexts;

        @Override
        public Object saveState(FacesContext context) {
            return new Object[]{
                    super.saveState(context),
                    function,
                    functionEditable,
                    originalClientId
            };
        }

        @Override
        public void restoreState(FacesContext context, Object stateObj) {
            Object[] state = (Object[]) stateObj;
            int i = 0;
            super.restoreState(context, state[i++]);
            function = (SummaryFunction) state[i++];
            functionEditable = (Boolean) state[i++];
            originalClientId  = (String) state[i++];
        }

        public CalculationContext getGlobalCalculationContext() {
            if (globalCalculationContext == null)
                globalCalculationContext = new CalculationContext();
            return globalCalculationContext;
        }

        public CalculationContext getGroupCalculationContext(RowGroup group) {
            if (groupCalculationContexts == null)
                groupCalculationContexts = new HashMap<RowGroup, CalculationContext>();
            CalculationContext calculationContext = groupCalculationContexts.get(group);
            if (calculationContext == null) {
                calculationContext = new CalculationContext();
                groupCalculationContexts.put(group, calculationContext);
            }
            return calculationContext;
        }

        private UsageContext getUsageContext() {
            if (usageContext == null)
                usageContext = new UsageContext((Summary) getComponent());
            return usageContext;
        }

        private DataTable getTable() {
            return getUsageContext().getTable();
        }

        private Summaries getSummaries() {
            return getUsageContext().getTable().getSummaries();
        }

        /**
         * The same Summary component can be rendered in different places depending on how it is declared. If it is declared
         * in the table's "header"/"footer" facet or the column's "header"/"footer" facet then the value for this Summary
         * component is calculated over all displayed rows, and it is rendered only once in the table's/column's
         * header/footer. In this case, such Summary is called a "global" one. But it can also be placed into one of the
         * grouping-related facets of the <o:column> tag, which will make the <o:summary> component to be rendered several
         * times -- once for each group (according to the facet in which it is declared). In this case, we have to maintain
         * several summary value calculator objects -- one for each RowGroup (which is identical to "one per each time it
         * is rendered"). This object contains all of the info for such single rendering instance.
         */
        private class CalculationContext {
            private SummaryFunction.Calculator calculator;
            private String renderedSummaryClientId;

            public Object getCalculator() {
                return calculator;
            }

            private void addValue(Object value) {
                if (calculator == null) {
                    SummaryFunction function = getUsageContext().getFunction();
                    if (function == null) return;
                    calculator = function.startCalculation();
                }
                calculator.addValue(value);
            }

            public String getRenderedSummaryClientId() {
                return renderedSummaryClientId;
            }

            public void setRenderedSummaryClientId(String renderedSummaryClientId) {
                this.renderedSummaryClientId = renderedSummaryClientId;
            }

            public void renderInitScript(ScriptBuilder sb, PopupMenu popupMenu, MenuItem selectedMenuItem) throws IOException {
                if (renderedSummaryClientId == null) {
                    // this summary component hasn't been rendered, and so...
                    // - we cannot initialize it without knowing its actual client id which could only be known if it was
                    //   rendered in the place where it is specified;
                    // - there's no point in initializing it if it's not rendered.
                    return;
                }
                Object summaryValue = calculator != null ? calculator.endCalculation() : "";
                UsageContext usageContext = getUsageContext();
                SummaryFunction function = usageContext.getFunction();
                Converter converter = usageContext.getConverter();
                FacesContext context = FacesContext.getCurrentInstance();
                if (converter == null && summaryValue != null) {
                    Class cls = summaryValue.getClass();
                    if (cls.equals(Double.class) || cls.equals(Float.class)) {
                        NumberConverter numberConverter = new NumberConverter();
                        numberConverter.setGroupingUsed(false);
                        numberConverter.setMaxFractionDigits(2);
                        converter = numberConverter;
                    } else {
                        converter = Rendering.getConverterForType(context, cls);
                    }
                }

                Object summaryValueStr = summaryValue != null
                        ? (converter != null ? converter.getAsString(context, getSummary(), summaryValue) : summaryValue.toString())
                        : null;

                ValueExpression patternExpression = getPatternExpression();
                Components.setRequestVariable("value", summaryValue);
                Components.setRequestVariable("valueString", summaryValueStr);
                Components.setRequestVariable("function", function);
                String summaryOutput = calculator != null && summaryValueStr != null
                        ? patternExpression.getValue(context.getELContext()).toString()
                        : "";
                Components.restoreRequestVariable("value");
                Components.restoreRequestVariable("valueString");
                Components.restoreRequestVariable("function");

                sb.functionCall("O$.Summary._init",
                        renderedSummaryClientId,
                        originalClientId,
                        summaryOutput,
                        usageContext.getTable(),
                        popupMenu,
                        selectedMenuItem,
                        popupMenu != null ? Resources.internalURL(context, null, SelectBooleanCheckboxImageManager.DEFAULT_SELECTED_IMAGE, true) : null,
                        popupMenu != null ? Resources.internalURL(context, null, SelectBooleanCheckboxImageManager.DEFAULT_UNSELECTED_IMAGE, true) : null
                ).semicolon();
            }
        }

        public void addCurrentRowValue() {
            if (!getUsageContext().isApplicableInThisContext()) return;

            ELContext elContext = FacesContext.getCurrentInstance().getELContext();

            Object value = getByValue(elContext);
            if (getUsageContext().getCalculatedGlobally()) {
                getGlobalCalculationContext().addValue(value);
            } else if (getUsageContext().getCalculatedForAllGroups()) {
                List<RowGroup> containingGroups = getContainingRowGroupsForThisRow();
                for (RowGroup group : containingGroups) {
                    CalculationContext calculationContext = getGroupCalculationContext(group);
                    calculationContext.addValue(value);
                }
            } else {
                String colId = getUsageContext().getCalculatedForGroupsWithColumnId();
                if (colId == null) {
                    // this summary instance does not require calculation in the current table's grouping state
                    return;
                }
                List<RowGroup> containingGroups = getContainingRowGroupsForThisRow();
                for (RowGroup group : containingGroups) {
                    if (group.getColumnId().equals(colId)) {
                        CalculationContext calculationContext = getGroupCalculationContext(group);
                        calculationContext.addValue(value);
                        break;
                    }
                }
            }
        }

        private Object getByValue(ELContext elContext) {
            UsageContext usageContext = getUsageContext();
            SummaryFunction function = usageContext.getFunction();
            if (function instanceof CountFunction) {
                // the actual value does not matter in case of the "Count" function, and we should allow the user not
                // to specify the "by" expression for this function, so we're skipping the actual "by" value calculation
                // attempt below.
                return "";
            }
            Object value = usageContext.getByValue(elContext);
            return value;
        }

        private List<RowGroup> getContainingRowGroupsForThisRow() {
            List<RowGroup> rowGroups = new ArrayList<RowGroup>();
            TableDataModel.RowInfo rowInfo = getTable().getModel().getRowInfo();
            addContainingRowGroupsForThisRow(rowGroups, rowInfo);
            return rowGroups;
        }

        private void addContainingRowGroupsForThisRow(List<RowGroup> rowGroups, TableDataModel.RowInfo rowInfo) {
            TableDataModel.RowInfo parentGroupRowInfo = rowInfo.getParentGroup();
            if (parentGroupRowInfo == null) return;
            GroupHeader groupHeader = (GroupHeader) parentGroupRowInfo.getRowData();
            RowGroup parentRowGroup = groupHeader.getRowGroup();
            rowGroups.add(parentRowGroup);
            addContainingRowGroupsForThisRow(rowGroups, parentGroupRowInfo);
        }

        private PopupMenu getMenuWithFunctions(FacesContext context, List<SummaryFunction> applicableFunctions) {
            PopupMenu popupMenu = null;
            UIComponent popupMenuContainer = getPopupMenuContainer(context);
            List<UIComponent> children = popupMenuContainer.getChildren();
            final String summaryFunctionsAttribute = "_summaryFunctions";
            for (UIComponent child : children) {
                PopupMenu m = (PopupMenu) child;
                List<SummaryFunction> summaryFunctions = (List<SummaryFunction>) m.getAttributes().get(summaryFunctionsAttribute);
                if (applicableFunctions.equals(summaryFunctions)) {
                    popupMenu = m;
                }
            }

            if (popupMenu == null) {
                final String idCounterAttr = "_idCounter";
                Integer idCounter = (Integer) popupMenuContainer.getAttributes().get(idCounterAttr);
                if (idCounter == null) idCounter = 0;
                popupMenu = Components.createComponent(context,
                        PopupMenu.COMPONENT_TYPE, PopupMenu.class, popupMenuContainer, "popupMenu" + idCounter++);
                popupMenuContainer.getAttributes().put(idCounterAttr, idCounter);
                popupMenu.setStandalone(true);

                popupMenu.getAttributes().put(summaryFunctionsAttribute, applicableFunctions);
                for (int i = 0, count = applicableFunctions.size(); i < count; i++) {
                    SummaryFunction function = applicableFunctions.get(i);
                    MenuItem menuItem = Components.createComponent(context, MenuItem.COMPONENT_TYPE, MenuItem.class, popupMenu, "item" + i);
                    String functionName = function.getName();
                    menuItem.setValue(functionName);
                    menuItem.setOnclick(new ScriptBuilder().functionCall(
                            "O$.Summary._setFunction", functionName.toLowerCase()
                    ).toString());
                    menuItem.setIconUrl(Resources.internalURL(context, null,
                            SelectBooleanCheckboxImageManager.DEFAULT_UNSELECTED_IMAGE, false));
                    menuItem.getAttributes().put(ATTR_FUNCTION_NAME, functionName);
                    popupMenu.getChildren().add(menuItem);
                }

                popupMenuContainer.getChildren().add(popupMenu);
            }

            return popupMenu;
        }

        private UIComponent getPopupMenuContainer(FacesContext context) {
            DataTable table = getTable();
            UIComponent facetContainer = table.getFacet(FACET_POPUP_MENUS_CONTAINER);
            if (facetContainer == null) {
                facetContainer = Components.createComponent(context, HtmlPanelGroup.COMPONENT_TYPE, UIComponent.class, table, "_popupMenuContainer_");
                table.getFacets().put(FACET_POPUP_MENUS_CONTAINER, facetContainer);
            }
            return facetContainer;
        }

        public void encodeAfterCalculation(FacesContext context) throws IOException {
            UsageContext usageContext = getUsageContext();
            if (!usageContext.isApplicableInThisContext()) return;

            MenuItem selectedMenuItem = null;
            PopupMenu popupMenu;
            if (getSummary().getFunctionEditable()) {
                List<SummaryFunction> applicableFunctions = usageContext.getApplicableFunctions();
                popupMenu = getMenuWithFunctions(context, applicableFunctions);
                encodePopupMenu(context, popupMenu);

                SummaryFunction currentFunction = usageContext.getFunction();
                String currentFunctionName = currentFunction.getName();
                List<UIComponent> children = popupMenu.getChildren();
                for (UIComponent child : children) {
                    if (!(child instanceof MenuItem)) continue;
                    MenuItem menuItem = (MenuItem) child;
                    String functionName = (String) child.getAttributes().get(ATTR_FUNCTION_NAME);
                    if (functionName.equals(currentFunctionName)) {
                        selectedMenuItem = menuItem;
                        break;
                    }
                }

            } else {
                popupMenu = null;
            }

            ScriptBuilder sb = new ScriptBuilder();
            if (globalCalculationContext != null) {
                if (globalCalculationContext.getRenderedSummaryClientId() == null) {
                    // this case might take place when this <o:summary> component is inserted into the "below" facet of
                    // <o:dataTable>, in which case it didn't have a chance to be rendered up to this moment and so its
                    // client id has not been initialized yet
                    Components.FacetReference parentFacetReference = Components.getParentFacetReference(getSummary());
                    if (parentFacetReference != null && parentFacetReference.getFacetName().equals(AbstractTable.FACET_BELOW)) {
                        DataTable table = getTable();
                        int prevRowIndex = table.getRowIndex();
                        if (prevRowIndex != -1) table.setRowIndex(-1);
                        try {
                            String clientId = getSummary().getClientId(context);
                            globalCalculationContext.setRenderedSummaryClientId(clientId);
                        } finally {
                            if (prevRowIndex != -1) table.setRowIndex(prevRowIndex);
                        }
                    }
                }
                globalCalculationContext.renderInitScript(sb, popupMenu, selectedMenuItem);
            }
            if (groupCalculationContexts != null) {
                Collection<CalculationContext> groupCalculationContexts = this.groupCalculationContexts.values();
                for (CalculationContext groupCalculationContext : groupCalculationContexts) {
                    groupCalculationContext.renderInitScript(sb, popupMenu, selectedMenuItem);
                }
            }

            Rendering.renderInitScript(context, sb, TableUtil.getTableUtilJsURL(context));
        }

        private Summary getSummary() {
            return (Summary) getComponent();
        }

        private void encodePopupMenu(FacesContext context, PopupMenu popupMenu) throws IOException {
            Map<String, Object> requestMap = context.getExternalContext().getRequestMap();
            final String renderedMenusParam = Summary.class.getName() + ".renderedMenus";
            Set<PopupMenu> renderedMenus = (Set<PopupMenu>) requestMap.get(renderedMenusParam);
            if (renderedMenus == null) {
                renderedMenus = new HashSet<PopupMenu>();
                requestMap.put(renderedMenusParam, renderedMenus);
            }

            if (renderedMenus.contains(popupMenu)) return;
            renderedMenus.add(popupMenu);

            popupMenu.encodeAll(context);
        }

        private ValueExpression getPatternExpression() {
            Summary summary = getSummary();
            ValueExpression expression = summary.getPattern();
            if (expression == null) {
                Summaries summaries = getSummaries();
                if (summaries != null) {
                    expression = summaries.getPattern();
                }
                if (expression == null) {
                    FacesContext context = FacesContext.getCurrentInstance();
                    ELContext elContext = context.getELContext();
                    expression = context.getApplication().getExpressionFactory().createValueExpression(
                            elContext, DEFAULT_PATTERN, Object.class);
                }
                summary.setPattern(expression);
            }
            return expression;
        }

    }


    public ValueExpression getBy() {
        return getValueExpression("by");
    }

    public void setBy(ValueExpression by) {
        setValueExpression("by", by);
    }

    public ValueExpression getPattern() {
        return getValueExpression(ATTR_PATTERN);
    }

    public void setPattern(ValueExpression valueExpression) {
        setValueExpression(ATTR_PATTERN, valueExpression);
    }

    public boolean getFunctionEditable() {
        StampState stampState = stampState();
        DataTable table = getParent() != null ? stampState.getUsageContext().getTable() : null;
        Summaries summaries = table != null ? table.getSummaries() : null;
        boolean defaultValue = summaries != null ? summaries.getFunctionEditable() : true;
        return ValueBindings.get(this, "functionEditable", stampState.functionEditable, defaultValue);
    }

    public void setFunctionEditable(boolean functionEditable) {
        stampState().functionEditable = functionEditable;
    }

    private static class UsageContext {
        private Summary summary;
        private DataTable table;
        private BaseColumn column;
        private boolean insideColumnsComponent;
        private String facetName;

        private Boolean applicableInThisContext;
        /**
         * "Common location" means that the summary is not bound to any table's column, but to the whole table or
         * column group
         */
        private boolean commonLocation;
        /**
         * "Global calculation" means that according to the summary component's declaration location it should be
         * calculated over all displayed rows and not over the individual groups (when row grouping feature is used)
         */
        private Boolean calculatedGlobally;
        private String calculatedForGroupsWithColumnId;
        private Boolean calculatedForAllGroups;

        private SummaryFunction function;

        public UsageContext(Summary summary) {
            this.summary = summary;

            Components.FacetReference facetReference = Components.getParentFacetReference(summary);
            FacesContext context = FacesContext.getCurrentInstance();
            if (facetReference == null)
                throw new FacesException("The <o:summary> tag can only be used inside of one of the facets of the " +
                        "<o:dataTable> component or inside of the facets of one of its <o:column> or <o:columnGroup> " +
                        "tags, but it wasn't placed into a facet: " +
                        summary.getClientId(context));
            UIComponent facetOwner = facetReference.getFacetOwner();

            AbstractTable abstractTable;
            if (facetOwner instanceof DataTable) {
                abstractTable = (AbstractTable) facetOwner;
            } else if (facetOwner instanceof Column || facetOwner instanceof ColumnGroup) {
                this.column = (BaseColumn) facetOwner;
                abstractTable = column.getTable();
                if (abstractTable == null)
                    throw new FacesException("The <o:summary> component is placed into a misplaced column component -" +
                            "the parent table for that column (" +
                            facetOwner.getClientId(context) +
                            ") could not be found in the components tree.");
            } else if (facetOwner instanceof Columns) {
                Columns columns = (Columns) facetOwner;
                abstractTable = columns.getTable();
                if (abstractTable == null)
                    throw new FacesException("The <o:summary> component is placed into a misplaced <o:columns> component -" +
                            "the parent table for that component (" +
                            facetOwner.getClientId(context) +
                            ") could not be found in the components tree.");
                int columnIndex = columns.getColumnIndex();
                List<DynamicColumn> dynamicColumns = columns.toColumnList(context);
                this.insideColumnsComponent = true;
                this.column = dynamicColumns.get(columnIndex);//columnIndex != -1 ? dynamicColumns.get(columnIndex) : null;
            } else {
                throw new FacesException("The <o:summary> tag must be placed as a facet inside of <o:dataTable>, " +
                        "<o:column> or <o:columnGroup> components. This summary component (" +
                        summary.getClientId(context) +
                        ") was placed into the \"" + facetReference.getFacetName() +
                        "\" facet of component with class " +
                        facetOwner.getClass().getName() + " (clientId=\"" +
                        facetOwner.getClientId(context) + "\"");
            }
            if (!(abstractTable instanceof DataTable)) {
                // e.g. in case of an attempt to use in inside of TreeTable
                throw new FacesException("The <o:summary> component can only be used with <o:dataTable> component " +
                        "currently: " + summary.getClientId(context));
            }
            this.table = (DataTable) abstractTable;

            this.facetName = facetReference.getFacetName();
            if (facetOwner == table || (equalsToOneOf(facetName,
                    BaseColumn.FACET_HEADER,
                    BaseColumn.FACET_SUB_HEADER,
                    BaseColumn.FACET_FOOTER))) {
                // it is either in a table's facet or one column's "global" facets
                this.calculatedGlobally = true;
                this.calculatedForGroupsWithColumnId = "";
                this.calculatedForAllGroups = false;
            } else {
                // it is in one of the column's grouping facets
                this.calculatedGlobally = false;

                RowGrouping rowGrouping = table.getRowGrouping();
                if (rowGrouping == null) {
                    // no calculation for this summary component is required at all since it's inside of the
                    // grouping-related facets, but grouping is turned off currently
                    this.calculatedForGroupsWithColumnId = "";
                    this.calculatedForAllGroups = false;
                } else if (equalsToOneOf(facetName, BaseColumn.FACET_GROUP_HEADER, BaseColumn.FACET_GROUP_FOOTER)) {
                    List<GroupingRule> groupingRules = rowGrouping.getGroupingRules();
                    String summaryOwnerColumnId = column.getId();
                    boolean groupedByThisColumn = false;
                    for (GroupingRule groupingRule : groupingRules) {
                        if (groupingRule.getColumnId().equals(summaryOwnerColumnId)) {
                            groupedByThisColumn = true;
                            break;
                        }
                    }
                    this.calculatedForGroupsWithColumnId = groupedByThisColumn ? summaryOwnerColumnId : "";
                    this.calculatedForAllGroups = false;
                } else if (equalsToOneOf(facetName, BaseColumn.FACET_IN_GROUP_HEADER, BaseColumn.FACET_IN_GROUP_FOOTER)) {
                    this.calculatedForGroupsWithColumnId = "";
                    this.calculatedForAllGroups = table.getRenderedColumns().contains(column);
                } else {
                    throw new IllegalStateException("Unexpected placement of <o:summary> component: " +
                            "facet owner component is " + facetOwner.getClass().getName() +
                            "; facet name is \"" + facetName + "\"");
                }
            }

            commonLocation = column == null || column instanceof ColumnGroup;
        }

        /**
         * Applicable for the implicitly-created Summary instances. This method checks if this implicitly-created
         * instance is applicable in the context where it is created. Applicable means that summary can be calculated
         * here and should be displayed. In other words this is a mechanism that detects whether summary should be
         * displayed in any given context (a facet inside of any given column).
         */
        public boolean isApplicableInThisContext() {
            if (applicableInThisContext == null) {
                if (!summary.implicitMode) {
                    applicableInThisContext = true;
                } else {
                    ValueExpression byExpression = getByExpression(false);
                    if (byExpression == null) {
                        applicableInThisContext = false;
                    } else {
                        SummaryFunction function = detectFunctionByColumn(byExpression);
                        applicableInThisContext = function != null && !(function instanceof CountFunction);
                    }
                }
            }
            return applicableInThisContext;
        }


        public boolean getCalculatedGlobally() {
            return calculatedGlobally;
        }

        public String getCalculatedForGroupsWithColumnId() {
            return !calculatedForGroupsWithColumnId.equals("") ? calculatedForGroupsWithColumnId : null;
        }

        public boolean getCalculatedForAllGroups() {
            return calculatedForAllGroups;
        }

        public DataTable getTable() {
            return table;
        }

        private ValueExpression byExpression;

        public ValueExpression getByExpression() {
            return getByExpression(true);
        }

        public Object getByValue(ELContext elContext) {
            DynamicColumn dynamicColumn = column instanceof DynamicColumn ? (DynamicColumn) column : null;
            Runnable restoreVariables = dynamicColumn != null ? dynamicColumn.enterComponentContext() : null;
            Object value = getByExpression().getValue(elContext);
            if (restoreVariables != null) restoreVariables.run();
            return value;
        }

        private ValueExpression getByExpression(boolean throwExceptions) {
            if (byExpression != null)
                return byExpression;

            byExpression = summary.getBy();
            if (byExpression != null)
                return byExpression;

            if (column == null) {
                if (!throwExceptions) return null;
                if (table.getRowIndex() != -1) {
                    // reset row index just to ensure a suffix-less table id in an exception message
                    table.setRowIndex(-1);
                }
                throw new FacesException("Could not detect the summary calculation expression for <o:summary> " +
                        "component in the table " + table.getClientId(FacesContext.getCurrentInstance()) + ". Either " +
                        "the \"by\" attribute has to be specified or <o:summary> should be placed into a column's " +
                        "facet to derive the expression from that column automatically.");
            }
            if (!(column instanceof Column)) {
                if (!throwExceptions) return null;
                throw new FacesException("<o:summary> component can only be used inside of <o:column>, but not other " +
                        "types of column tags (" + column.getClass().getName() + ")");
            }
            byExpression = column.getColumnValueExpression();
            // the summary's expression is not specified explicitly and it's not inside of a column whose value
            // expression can be detected
            if (byExpression == null) {
                if (!throwExceptions) return null;
                if (table.getRowIndex() != -1) {
                    // reset row index just to ensure a suffix-less table id in an exception message
                    table.setRowIndex(-1);
                }
                throw new FacesException("Could not detect the summary calculation expression for " +
                        "<o:summary> component in the table " + table.getClientId(FacesContext.getCurrentInstance()) +
                        ". Neither the \"by\" attribute is specified, nor the value for the parent column could be " +
                        "detected (the column doesn't have the \"value\" attribute).");
            }
            return byExpression;
        }

        /**
         * Gets the user-specified function or detects it automatically if not specified in this Summary component
         * explicitly.
         */
        public SummaryFunction getFunction() {
            if (function != null) return function;

            function = summary.getFunction();
            if (function != null) return function;

            function = detectFunctionByColumn(commonLocation ? null : getByExpression());

            return function;
        }

        private SummaryFunction detectFunctionByColumn(ValueExpression byExpression) {
            if (commonLocation) {
                return new CountFunction();
            } else {
                BaseColumn.ExpressionData expressionData = column.getExpressionData(byExpression);
                Class valueType = expressionData.getValueType();
                return getDefaultFunctionForType(valueType);
            }
        }

        public List<SummaryFunction> getApplicableFunctions() {
            if (commonLocation) {
                ValueExpression byExpression = summary.getBy();
                List<BaseColumn> allColumns = table.getAllColumns();
                BaseColumn anyColumn = allColumns.size() > 0 ? allColumns.get(0) : null;
                if (byExpression != null && anyColumn != null) {
                    BaseColumn.ExpressionData expressionData = anyColumn.getExpressionData(byExpression);
                    Class valueType = expressionData.getValueType();
                    return getFunctionsForType(valueType);
                } else {
                    return Collections.singletonList((SummaryFunction) new CountFunction());
                }
            } else {
                ValueExpression byExpression = getByExpression();
                BaseColumn.ExpressionData expressionData = column.getExpressionData(byExpression);
                Class valueType = expressionData.getValueType();
                return getFunctionsForType(valueType);
            }
        }

        private Converter getConverter() {
            return summary.getConverter();
        }

        private static boolean equalsToOneOf(String str, String... toOneOf) {
            for (String oneOf : toOneOf) {
                if (str.equals(oneOf))
                    return true;
            }
            return false;
        }
    }

    private static List<SummaryFunction> getFunctionsForType(Class valueType) {
        List<SummaryFunction> functions = new ArrayList<SummaryFunction>();

        List<SummaryFunction> allFunctions = ApplicationParams.getSummaryFunctions();
        for (SummaryFunction fn : allFunctions) {
            if (fn.isApplicableForClass(valueType)) {
                functions.add(fn);
            }
        }
        return functions;

    }

    private static SummaryFunction getDefaultFunctionForType(Class valueType) {
        List<SummaryFunction> applicableFunctions = getFunctionsForType(valueType);
        if (applicableFunctions.size() > 0)
            return applicableFunctions.get(0);
        return new CountFunction();
    }


    public SummaryFunction getFunction() {
        return ValueBindings.get(this, "function", stampState().function, SummaryFunction.class);
    }

    public void setFunction(SummaryFunction function) {
        stampState().function = function;
    }


    public void addCurrentRowValue() {
        stampState().addCurrentRowValue();
    }

    @Override
    public void decode(FacesContext context) {
        super.decode(context);
        Map<String, String> requestParameterMap = context.getExternalContext().getRequestParameterMap();
        String functionName = requestParameterMap.get(stampState().originalClientId + "::setFunction");
        if (functionName != null) {
            SummaryFunction summaryFunction = ApplicationParams.getSummaryFunctionByName(functionName);
            if (summaryFunction == null)
                throw new IllegalArgumentException("Invalid summary function name -- no function with this name could be found: " + summaryFunction);
            setFunction(summaryFunction);
        }
    }

    @Override
    public void processUpdates(FacesContext context) {
        super.processUpdates(context);
        StampState stampState = stampState();
        if (stampState.function != null && ValueBindings.set(this, "function", stampState.function))
            stampState.function = null;
    }

    @Override
    public void encodeBegin(FacesContext context) throws IOException {
        StampState stampState = stampState();
        UsageContext usageContext = stampState.getUsageContext();
        if (!usageContext.isApplicableInThisContext()) return;

        DataTable table = stampState.getTable();// getTable invocation validates the parent tag
        super.encodeBegin(context);
        String clientId = getClientId(context);
        if (usageContext.getCalculatedGlobally()) {
            stampState.getGlobalCalculationContext().setRenderedSummaryClientId(clientId);
        } else {
            Object rowData = table.getRowData();
            if (rowData instanceof GroupHeaderOrFooter) {
                GroupHeaderOrFooter groupHeaderOrFooter = (GroupHeaderOrFooter) rowData;
                RowGroup rowGroup = groupHeaderOrFooter.getRowGroup();
                StampState.CalculationContext groupCalculationContext = stampState.getGroupCalculationContext(rowGroup);
                groupCalculationContext.setRenderedSummaryClientId(clientId);
            } else {
                throw new IllegalStateException("The <o:summary> tag should either be placed into one of <o:dataTable> " +
                        "component's facets or in one of the <o:column>/<o:columnGroup> facets of the appropriate table " +
                        "component.");
            }
        }
    }

    public void encodeAfterCalculation(FacesContext context) throws IOException {
        stampState().encodeAfterCalculation(context);
    }

    public void prepare(FacesContext context) {
        StampState stampState = stampState();
        UsageContext usageContext = stampState.getUsageContext();
        if (usageContext.getTable().getRowIndex() != -1)
            throw new IllegalArgumentException("table's rowIndex is expected to be -1 when invoking the " +
                    "prepare method for it to be able to detect its 'original' client id");
        stampState.originalClientId = getClientId(context);
    }


}
TOP

Related Classes of org.openfaces.component.table.Summary

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.