net.sf.jasperreports.engine.fill.DelayedFillActions.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.fill.DelayedFillActions.java

Source

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * JasperReports 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 JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.engine.fill;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;

import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRVirtualizable;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.PrintElementVisitor;
import net.sf.jasperreports.engine.base.JRVirtualPrintPage;
import net.sf.jasperreports.engine.base.VirtualElementsData;
import net.sf.jasperreports.engine.base.VirtualizablePageElements;
import net.sf.jasperreports.engine.type.EvaluationTimeEnum;
import net.sf.jasperreports.engine.util.LinkedMap;
import net.sf.jasperreports.engine.util.UniformPrintElementVisitor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 */
public class DelayedFillActions implements VirtualizationListener<VirtualElementsData> {
    private static final Log log = LogFactory.getLog(DelayedFillActions.class);

    protected static final String FILL_CACHE_KEY_ID = DelayedFillActions.class.getName() + "#id";
    public static final String EXCEPTION_MESSAGE_KEY_ELEMENT_NOT_FOUND = "fill.delayed.fill.actions.element.not.found";

    private final int id;
    private final BaseReportFiller reportFiller;
    private final JRFillContext fillContext;

    // we can use HashMap because the map is initialized in the beginning and doesn't change afterwards
    private final HashMap<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> actionsMap;

    private Map<Integer, JRFillElement> fillElements;
    private Set<Integer> masterFillElementIds;

    private Set<JRVirtualizationContext> listenedContexts;

    private Set<Integer> transferredIds;

    public DelayedFillActions(BaseReportFiller reportFiller) {
        this.id = assignId(reportFiller);
        this.reportFiller = reportFiller;
        this.fillContext = reportFiller.fillContext;
        this.actionsMap = new HashMap<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>>();
        this.fillElements = new HashMap<Integer, JRFillElement>();
        this.masterFillElementIds = new HashSet<Integer>();
        this.listenedContexts = new HashSet<JRVirtualizationContext>();
    }

    private static int assignId(BaseReportFiller reportFiller) {
        AtomicInteger counter = (AtomicInteger) reportFiller.fillContext.getFillCache(FILL_CACHE_KEY_ID);
        if (counter == null) {
            // we just need a mutable integer, there's no actual concurrency here
            counter = new AtomicInteger();
            reportFiller.fillContext.setFillCache(FILL_CACHE_KEY_ID, counter);
        }

        return counter.incrementAndGet();
    }

    public int getId() {
        return id;
    }

    public void createDelayedEvaluationTime(JREvaluationTime evaluationTime) {
        LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> evaluationActions = new LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>();
        actionsMap.put(evaluationTime, evaluationActions);
    }

    protected void registerPage(JRPrintPage page) {
        if (page instanceof JRVirtualPrintPage) {
            JRVirtualizationContext virtualizationContext = ((JRVirtualPrintPage) page).getVirtualizationContext();
            if (!listenedContexts.contains(virtualizationContext)) {
                //FIXMEBOOK part reports use a single context which will collect all listeners
                virtualizationContext.addListener(this);
                listenedContexts.add(virtualizationContext);

                if (log.isDebugEnabled()) {
                    log.debug(id + " registered virtualization listener on " + virtualizationContext);
                }
            }
        }
    }

    public void dispose() {
        for (JRVirtualizationContext virtualizationContext : listenedContexts) {
            virtualizationContext.removeListener(this);

            if (log.isDebugEnabled()) {
                log.debug(id + " unregistered virtualization listener on " + virtualizationContext);
            }
        }
    }

    public void addDelayedAction(JRFillElement element, JRPrintElement printElement,
            JREvaluationTime evaluationTime, FillPageKey pageKey) {
        registerFillElement(element, evaluationTime);

        ElementEvaluationAction action = new ElementEvaluationAction(element, printElement);
        addDelayedAction(printElement, action, evaluationTime, pageKey);
    }

    protected void registerFillElement(JRFillElement element, JREvaluationTime evaluationTime) {
        int fillElementId = element.printElementOriginator.getSourceElementId();
        if (!fillElements.containsKey(fillElementId)) {
            fillElements.put(fillElementId, element);

            if (evaluationTime.getType() == EvaluationTimeEnum.MASTER) {
                masterFillElementIds.add(fillElementId);
            }
        }
    }

    protected void registerTransferredId(int sourceId) {
        if (transferredIds == null) {
            transferredIds = new HashSet<Integer>();
        }

        // duplicates are handled
        boolean added = transferredIds.add(sourceId);
        if (added && log.isDebugEnabled()) {
            log.debug(id + " transferred id " + sourceId);
        }
    }

    public void addDelayedAction(Object actionKey, EvaluationBoundAction action, JREvaluationTime evaluationTime,
            FillPageKey pageKey) {
        if (log.isDebugEnabled()) {
            log.debug(id + " adding delayed action " + action + " at " + evaluationTime + ", key " + pageKey);
        }

        // get the pages map for the evaluation
        LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> pagesMap = actionsMap
                .get(evaluationTime);

        fillContext.lockVirtualizationContext();
        try {
            synchronized (pagesMap) {
                // get the actions map for the current page, creating if it does not yet exist
                LinkedMap<Object, EvaluationBoundAction> boundElementsMap = pageActionsMap(pagesMap, pageKey);

                // add the delayed element action to the map
                boundElementsMap.add(actionKey, action);
            }
        } finally {
            fillContext.unlockVirtualizationContext();
        }
    }

    protected LinkedMap<Object, EvaluationBoundAction> pageActionsMap(
            LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map, FillPageKey pageKey) {
        LinkedMap<Object, EvaluationBoundAction> pageMap = map.get(pageKey);
        if (pageMap == null) {
            pageMap = new LinkedMap<Object, EvaluationBoundAction>();
            map.put(pageKey, pageMap);

            registerPage(pageKey.page);
        }
        return pageMap;
    }

    public void runActions(JREvaluationTime evaluationTime, byte evaluation) throws JRException {
        if (log.isDebugEnabled()) {
            log.debug(id + " running delayed actions on " + evaluationTime);
        }

        LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> pagesMap = actionsMap
                .get(evaluationTime);

        boolean hasEntry;
        do {
            reportFiller.checkInterrupted();

            // locking once per page so that we don't hold the lock for too long
            // (that would prevent async exporters from getting page data during a long resolve)
            fillContext.lockVirtualizationContext();
            try {
                synchronized (pagesMap) {
                    // resolve a single page
                    Iterator<Map.Entry<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> pagesIt = pagesMap
                            .entrySet().iterator();
                    hasEntry = pagesIt.hasNext();
                    if (hasEntry) {
                        Map.Entry<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> pageEntry = pagesIt.next();
                        int pageIdx = pageEntry.getKey().index;

                        if (log.isDebugEnabled()) {
                            log.debug(
                                    id + " running actions for page " + pageEntry.getKey().page + " at " + pageIdx);
                        }

                        StandardBoundActionExecutionContext context = new StandardBoundActionExecutionContext();
                        context.setCurrentPageIndex(pageIdx);
                        JasperPrint jasperPrint = fillContext.getMasterFiller().getJasperPrint();
                        context.setTotalPages(jasperPrint.getPages().size());
                        context.setEvaluationTime(evaluationTime);
                        context.setExpressionEvaluationType(evaluation);

                        LinkedMap<Object, EvaluationBoundAction> boundElementsMap = pageEntry.getValue();
                        // execute the actions
                        while (!boundElementsMap.isEmpty()) {
                            EvaluationBoundAction action = boundElementsMap.pop();
                            action.execute(context);
                        }

                        // remove the entry from the pages map
                        pagesIt.remove();

                        // call the listener to signal that the page has been modified
                        if (reportFiller.fillListener != null) {
                            reportFiller.fillListener.pageUpdated(jasperPrint, pageIdx);
                        }
                    }
                }
            } finally {
                fillContext.unlockVirtualizationContext();
            }
        } while (hasEntry);
    }

    public boolean hasDelayedActions(JRPrintPage page) {
        FillPageKey pageKey = new FillPageKey(page);
        for (LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map : actionsMap.values()) {
            fillContext.lockVirtualizationContext();
            try {
                synchronized (map) {
                    LinkedMap<Object, EvaluationBoundAction> boundMap = map.get(pageKey);
                    if (boundMap != null && !boundMap.isEmpty()) {
                        return true;
                    }
                }
            } finally {
                fillContext.unlockVirtualizationContext();
            }
        }

        return false;
    }

    protected boolean hasMasterDelayedActions(JRPrintPage page) {
        LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> masterActions = actionsMap
                .get(JREvaluationTime.EVALUATION_TIME_MASTER);
        FillPageKey pageKey = new FillPageKey(page);

        fillContext.lockVirtualizationContext();
        try {
            synchronized (masterActions)//FIXME is this necessary?
            {
                LinkedMap<Object, EvaluationBoundAction> pageMasterActions = masterActions.get(pageKey);
                return pageMasterActions != null && !pageMasterActions.isEmpty();
            }
        } finally {
            fillContext.unlockVirtualizationContext();
        }
    }

    public void moveActions(FillPageKey fromKey, FillPageKey toKey) {
        if (log.isDebugEnabled()) {
            log.debug(id + " moving actions from " + fromKey + " to " + toKey);
        }

        for (LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map : actionsMap.values()) {
            fillContext.lockVirtualizationContext();
            try {
                synchronized (map) {
                    LinkedMap<Object, EvaluationBoundAction> subreportMap = map.remove(fromKey);
                    if (subreportMap != null && !subreportMap.isEmpty()) {
                        LinkedMap<Object, EvaluationBoundAction> masterMap = pageActionsMap(map, toKey);
                        masterMap.addAll(subreportMap);
                    }
                }
            } finally {
                fillContext.unlockVirtualizationContext();
            }
        }
    }

    @Override
    public void beforeExternalization(JRVirtualizable<VirtualElementsData> object) {
        JRVirtualizationContext virtualizationContext = object.getContext();
        virtualizationContext.lock();//already locked in ElementsBlock.beforeExternalization()
        try {
            writeElementEvaluations(object);
        } finally {
            virtualizationContext.unlock();
        }
    }

    protected void writeElementEvaluations(final JRVirtualizable<VirtualElementsData> object) {
        if (log.isDebugEnabled()) {
            log.debug(id + " setting element evaluation for elements in " + object.getUID());
        }

        JRVirtualPrintPage page = ((VirtualizablePageElements) object).getPage();// ugly but needed for now
        FillPageKey pageKey = new FillPageKey(page);
        VirtualElementsData virtualData = object.getVirtualData();

        final Map<JREvaluationTime, Map<JRPrintElement, Integer>> evaluations = new LinkedHashMap<>();
        ElementEvaluationsCollector collector = new ElementEvaluationsCollector() {
            @Override
            public void collect(JRPrintElement printElement, JRFillElement fillElement,
                    JREvaluationTime evaluationTime) {
                Map<JRPrintElement, Integer> elementEvaluations = evaluations.get(evaluationTime);
                if (elementEvaluations == null) {
                    // collection delayed evaluations for elements that are about to be externalized.
                    // the evaluations store the ID of the fill elements in order to serialize the data.
                    elementEvaluations = new LinkedHashMap<JRPrintElement, Integer>();
                    evaluations.put(evaluationTime, elementEvaluations);
                }

                elementEvaluations.put(printElement, fillElement.printElementOriginator.getSourceElementId());
            }
        };

        doCollectElementEvaluations(page, virtualData.getElements(), collector, false);

        for (Map.Entry<JREvaluationTime, Map<JRPrintElement, Integer>> evalEntry : evaluations.entrySet()) {
            JREvaluationTime evaluationTime = evalEntry.getKey();
            Map<JRPrintElement, Integer> elementEvaluations = evalEntry.getValue();

            // save the evaluations in the virtual data
            virtualData.setElementEvaluations(id, evaluationTime, elementEvaluations);

            // add an action for the page so that it gets devirtualized on resolveBoundElements
            VirtualizedPageEvaluationAction virtualizedAction = new VirtualizedPageEvaluationAction(object, id);
            LinkedMap<Object, EvaluationBoundAction> pageActions = actionsMap.get(evaluationTime).get(pageKey);
            pageActions.add(null, virtualizedAction);

            if (log.isDebugEnabled()) {
                log.debug(id + " created action " + virtualizedAction);
            }
        }
    }

    @Override
    public void afterInternalization(JRVirtualizable<VirtualElementsData> object) {
        JRVirtualizationContext virtualizationContext = object.getContext();
        virtualizationContext.lock();
        try {
            readElementEvaluations(object);
        } finally {
            virtualizationContext.unlock();
        }
    }

    protected void readElementEvaluations(JRVirtualizable<VirtualElementsData> object) {
        JRVirtualPrintPage page = ((VirtualizablePageElements) object).getPage();// ugly but needed for now
        FillPageKey pageKey = new FillPageKey(page);

        for (Map.Entry<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> boundMapEntry : actionsMap
                .entrySet()) {
            JREvaluationTime evaluationTime = boundMapEntry.getKey();
            LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map = boundMapEntry.getValue();

            synchronized (map) {
                LinkedMap<Object, EvaluationBoundAction> actionsMap = map.get(pageKey);
                readElementEvaluations(object, id, evaluationTime, actionsMap);

                if (transferredIds != null) {
                    //FIXMEBOOK does this have any effect on the order of the actions?
                    for (Integer transferredId : transferredIds) {
                        readElementEvaluations(object, transferredId, evaluationTime, actionsMap);
                    }
                }
            }
        }
    }

    protected void readElementEvaluations(JRVirtualizable<VirtualElementsData> object, int sourceId,
            JREvaluationTime evaluationTime, LinkedMap<Object, EvaluationBoundAction> actionsMap) {
        // get the delayed evaluations from the devirtualized data and add it back
        // to the filler delayed evaluation maps.
        VirtualElementsData elementsData = object.getVirtualData();
        Map<JRPrintElement, Integer> elementEvaluations = elementsData.getElementEvaluations(sourceId,
                evaluationTime);
        if (elementEvaluations != null) {
            for (Map.Entry<JRPrintElement, Integer> entry : elementEvaluations.entrySet()) {
                JRPrintElement element = entry.getKey();
                int fillElementId = entry.getValue();
                JRFillElement fillElement = fillElements.get(fillElementId);

                if (log.isDebugEnabled()) {
                    log.debug(id + " got evaluation " + evaluationTime + ", source id " + sourceId + ", on "
                            + element + ", from object " + object + ", using " + fillElement);
                }

                if (fillElement == null) {
                    throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_ELEMENT_NOT_FOUND,
                            new Object[] { fillElementId });
                }

                // add first so that it will be executed immediately
                actionsMap.addFirst(element, new ElementEvaluationAction(fillElement, element));
            }
        }
    }

    public void moveMasterEvaluations(DelayedFillActions sourceActions, JRPrintPage page, int pageIndex) {
        FillPageKey sourcePageKey = new FillPageKey(page);
        FillPageKey destinationPageKey = new FillPageKey(page, pageIndex);
        moveMasterEvaluations(sourceActions, sourcePageKey, destinationPageKey);
    }

    public void moveMasterEvaluations(DelayedFillActions sourceActions, FillPageKey pageKey) {
        moveMasterEvaluations(sourceActions, pageKey, pageKey);
    }

    protected void moveMasterEvaluations(DelayedFillActions sourceActions, FillPageKey sourcePageKey,
            FillPageKey destinationPageKey) {
        if (log.isDebugEnabled()) {
            log.debug(id + " moving master actions from " + sourceActions.id + ", source " + sourcePageKey
                    + ", destination " + destinationPageKey);
        }

        fillContext.lockVirtualizationContext();
        try {
            LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> actions = sourceActions.actionsMap
                    .get(JREvaluationTime.EVALUATION_TIME_MASTER);
            synchronized (actions) {
                LinkedMap<Object, EvaluationBoundAction> pageActions = actions.remove(sourcePageKey);//FIXMEBOOK deregister virt listener
                if (pageActions == null || pageActions.isEmpty()) {
                    return;
                }

                moveMasterActions(pageActions, destinationPageKey);

                // copy fill elements Ids for all master actions
                for (Integer elementId : sourceActions.masterFillElementIds) {
                    if (!fillElements.containsKey(elementId)) {
                        fillElements.put(elementId, sourceActions.fillElements.get(elementId));
                        masterFillElementIds.add(elementId);
                    }
                }
            }
        } finally {
            fillContext.unlockVirtualizationContext();
        }
    }

    protected void moveMasterActions(LinkedMap<Object, EvaluationBoundAction> sourceActions,
            FillPageKey destinationPageKey) {
        LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> masterActions = actionsMap
                .get(JREvaluationTime.EVALUATION_TIME_MASTER);
        synchronized (masterActions) {
            LinkedMap<Object, EvaluationBoundAction> masterPageActions = pageActionsMap(masterActions,
                    destinationPageKey);

            while (!sourceActions.isEmpty()) {
                Map.Entry<Object, EvaluationBoundAction> entry = sourceActions.popEntry();
                Object key = entry.getKey();
                EvaluationBoundAction action = entry.getValue();
                masterPageActions.add(key, action);
                actionMoved(action);

                if (log.isDebugEnabled()) {
                    log.debug(id + " moved action " + action);
                }
            }
        }
    }

    protected void actionMoved(EvaluationBoundAction action) {
        if (action instanceof VirtualizedPageEvaluationAction)//ugly
        {
            int sourceId = ((VirtualizedPageEvaluationAction) action).getSourceId();
            registerTransferredId(sourceId);
        }
    }

    public void collectElementEvaluations(JRPrintPage page, List<JRPrintElement> elements,
            final ElementEvaluationsCollector collector) {
        fillContext.lockVirtualizationContext();
        try {
            doCollectElementEvaluations(page, elements, collector, true);
        } finally {
            fillContext.unlockVirtualizationContext();
        }
    }

    protected void doCollectElementEvaluations(JRPrintPage page, List<JRPrintElement> elements,
            final ElementEvaluationsCollector collector, boolean clearEmpty) {
        FillPageKey pageKey = new FillPageKey(page);

        for (Map.Entry<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> boundMapEntry : actionsMap
                .entrySet()) {
            final JREvaluationTime evaluationTime = boundMapEntry.getKey();
            LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map = boundMapEntry.getValue();

            synchronized (map) {
                final LinkedMap<Object, EvaluationBoundAction> actionsMap = map.get(pageKey);

                if (actionsMap != null && !actionsMap.isEmpty()) {
                    // FIXME optimize for pages with a single virtual block
                    // create a deep element visitor
                    PrintElementVisitor<Void> visitor = new UniformPrintElementVisitor<Void>(true) {
                        @Override
                        protected void visitElement(JRPrintElement element, Void arg) {
                            // remove the action from the map because we're saving it as part of the page.
                            // ugly cast but acceptable for now.
                            ElementEvaluationAction action = (ElementEvaluationAction) actionsMap.remove(element);
                            if (action != null) {
                                if (log.isDebugEnabled()) {
                                    log.debug(id + " collecting evaluation " + evaluationTime + " of element "
                                            + element);
                                }
                                collector.collect(element, action.element, evaluationTime);
                            }
                        }
                    };

                    for (JRPrintElement element : elements) {
                        element.accept(visitor, null);
                    }

                    if (clearEmpty && actionsMap.isEmpty()) {
                        map.remove(pageKey);
                    }
                }
            }
        }
    }

    public void addElementEvaluations(JRPrintPage page, int pageIndex, ElementEvaluationsSource source) {
        FillPageKey pageKey = new FillPageKey(page, pageIndex);

        for (Map.Entry<JREvaluationTime, LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>>> boundMapEntry : actionsMap
                .entrySet()) {
            JREvaluationTime evaluationTime = boundMapEntry.getKey();
            LinkedHashMap<FillPageKey, LinkedMap<Object, EvaluationBoundAction>> map = boundMapEntry.getValue();

            synchronized (map) {
                Map<JRPrintElement, JRFillElement> elementEvaluations = source.getEvaluations(evaluationTime);
                if (elementEvaluations != null) {
                    LinkedMap<Object, EvaluationBoundAction> actionsMap = pageActionsMap(map, pageKey);

                    for (Map.Entry<JRPrintElement, JRFillElement> entry : elementEvaluations.entrySet()) {
                        JRPrintElement element = entry.getKey();
                        JRFillElement fillElement = entry.getValue();

                        if (log.isDebugEnabled()) {
                            log.debug(id + " got evaluation " + evaluationTime + ", source id "
                                    + fillElement.printElementOriginator.getSourceElementId() + ", on " + element
                                    + ", using " + fillElement);
                        }

                        actionsMap.add(element, new ElementEvaluationAction(fillElement, element));
                    }
                }

            }
        }
    }

}