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

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.engine.fill.JRBaseFiller.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/>.
 */

/*
 * Contributors:
 * John Bindel - jbindel@users.sourceforge.net
 */

package net.sf.jasperreports.engine.fill;

import java.text.Format;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import org.apache.commons.javaflow.api.continuable;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import net.sf.jasperreports.engine.JRBand;
import net.sf.jasperreports.engine.JRDefaultStyleProvider;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRGroup;
import net.sf.jasperreports.engine.JROrigin;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRReport;
import net.sf.jasperreports.engine.JRReportTemplate;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.JRStyleSetter;
import net.sf.jasperreports.engine.JRTemplate;
import net.sf.jasperreports.engine.JRTemplateReference;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.base.JRBasePrintPage;
import net.sf.jasperreports.engine.base.JRVirtualPrintPage;
import net.sf.jasperreports.engine.type.BandTypeEnum;
import net.sf.jasperreports.engine.type.EvaluationTimeEnum;
import net.sf.jasperreports.engine.type.OrientationEnum;
import net.sf.jasperreports.engine.type.PrintOrderEnum;
import net.sf.jasperreports.engine.type.PropertyEvaluationTimeEnum;
import net.sf.jasperreports.engine.type.RunDirectionEnum;
import net.sf.jasperreports.engine.type.SectionTypeEnum;
import net.sf.jasperreports.engine.type.WhenNoDataTypeEnum;
import net.sf.jasperreports.engine.type.WhenResourceMissingTypeEnum;
import net.sf.jasperreports.engine.util.JRDataUtils;
import net.sf.jasperreports.engine.util.JRStyledTextParser;
import net.sf.jasperreports.engine.util.JRStyledTextUtil;
import net.sf.jasperreports.engine.util.StyleResolver;
import net.sf.jasperreports.repo.RepositoryContext;
import net.sf.jasperreports.repo.RepositoryResourceContext;
import net.sf.jasperreports.repo.SimpleRepositoryContext;
import net.sf.jasperreports.repo.SimpleRepositoryResourceContext;

/**
 * @author Teodor Danciu (teodord@users.sourceforge.net)
 */
public abstract class JRBaseFiller extends BaseReportFiller implements JRDefaultStyleProvider {

    private static final Log log = LogFactory.getLog(JRBaseFiller.class);

    public static final String EXCEPTION_MESSAGE_KEY_INFINITE_LOOP_CREATING_NEW_PAGE = "fill.common.filler.infinite.loop.creating.new.page";
    public static final String EXCEPTION_MESSAGE_KEY_COLUMN_HEADER_OVERFLOW_INFINITE_LOOP = "fill.common.filler.column.header.overflow.infinite.loop";
    public static final String EXCEPTION_MESSAGE_KEY_CIRCULAR_DEPENDENCY_FOUND = "fill.base.filler.circular.dependency.found";
    public static final String EXCEPTION_MESSAGE_KEY_EXTERNAL_STYLE_NAME_NOT_SET = "fill.base.filler.external.style.name.not.set";
    public static final String EXCEPTION_MESSAGE_KEY_NO_SUCH_GROUP = "fill.base.filler.no.such.group";
    public static final String EXCEPTION_MESSAGE_KEY_PAGE_HEADER_OVERFLOW_INFINITE_LOOP = "fill.common.filler.page.header.overflow.infinite.loop";
    public static final String EXCEPTION_MESSAGE_KEY_UNSUPPORTED_REPORT_SECTION_TYPE = "fill.base.filler.unsupported.report.section.type";
    public static final String EXCEPTION_MESSAGE_KEY_KEEP_TOGETHER_CONTENT_DOES_NOT_FIT = "fill.common.filler.keep.together.content.does.not.fit";

    private static final int PAGE_HEIGHT_PAGINATION_IGNORED = 0x7d000000;//less than Integer.MAX_VALUE to avoid 
    private static final int PAGE_WIDTH_IGNORED = 0x7d000000;

    protected BandReportFillerParent bandReportParent;

    private JRStyledTextParser styledTextParser = JRStyledTextParser.getInstance();

    /**
     *
     */
    protected String name;

    protected int columnCount;

    protected PrintOrderEnum printOrder;

    protected RunDirectionEnum columnDirection;

    protected int pageWidth;
    protected int maxPageWidth;

    protected int pageHeight;

    protected OrientationEnum orientation;

    private WhenNoDataTypeEnum whenNoDataType;

    protected int columnWidth;

    protected int columnSpacing;

    protected int leftMargin;

    protected int rightMargin;

    protected int topMargin;

    protected int bottomMargin;

    protected boolean isTitleNewPage;

    protected boolean isSummaryNewPage;

    protected boolean isSummaryWithPageHeaderAndFooter;

    protected boolean isFloatColumnFooter;

    /**
     * the resource missing handling type
     */
    protected WhenResourceMissingTypeEnum whenResourceMissingType;

    protected JRFillReportTemplate[] reportTemplates;

    protected List<ReportTemplateSource> templates;

    protected JRStyle defaultStyle;

    protected StyleResolver styleResolver;

    protected JRStyle[] styles;

    protected JRFillGroup[] groups;

    protected JRFillSection missingFillSection;
    protected JRFillBand missingFillBand;

    protected JRFillBand background;

    protected JRFillBand title;

    protected JRFillBand pageHeader;

    protected JRFillBand columnHeader;

    protected JRFillSection detailSection;

    protected JRFillBand columnFooter;

    protected JRFillBand pageFooter;

    protected JRFillBand lastPageFooter;

    protected JRFillBand summary;

    protected JRFillBand noData;

    protected JRPrintPage printPage;

    /**
     * List of {@link JRFillBand JRFillBand} objects containing all bands of the
     * report.
     */
    protected List<JRBand> bands;

    /**
     * Collection of subfillers
     */
    protected Map<Integer, JRBaseFiller> subfillers;

    private boolean bandOverFlowAllowed;

    /**
     *
     */
    protected Map<String, Format> dateFormatCache = new HashMap<String, Format>();
    protected Map<String, Format> numberFormatCache = new HashMap<String, Format>();

    protected GroupFooterElementRange groupFooterPositionElementRange;
    // we need to keep detail element range separate from orphan group footer element range
    // because it is created in advance, as by the time we need to create the element range 
    // for the current detail, we do not know if any group will break and footers would be filled
    protected ElementRange detailElementRange;
    // we use this element range to keep the detail element range once orphan footers start to render;
    // this is more like a flag to signal that we are currently dealing with orphan group footers
    protected ElementRange orphanGroupFooterDetailElementRange;
    // we keep the content of orphan group footers bands in separate element range because in horizontal
    // filler, the detail element range can have a different columnIndex and thus be moved with a different X offset
    protected ElementRange orphanGroupFooterElementRange;
    // keep the content of floating column footer so that it can be moved up in case content is moved
    // from one page/column to the next due to keep together, min details or orphan footer groups
    protected ElementRange floatColumnFooterElementRange;

    /**
     *
     */
    protected boolean isCreatingNewPage;
    protected boolean isNewPage;
    protected boolean isNewColumn;
    protected boolean isFirstPageBand;
    protected boolean isFirstColumnBand;
    // indicates if values from current record have already appeared on current page through a band being evaluated with JRExpression.EVALUATION_DEFAULT
    protected boolean isCrtRecordOnPage;
    protected boolean isCrtRecordOnColumn;
    // we call it min level because footers print in reverse order and lower level means outer footer
    protected Integer preventOrphanFootersMinLevel;
    protected int crtGroupFootersLevel;

    protected int columnIndex;

    protected int offsetX;
    protected int offsetY;
    protected int columnHeaderOffsetY;
    protected int columnFooterOffsetY;
    protected int lastPageColumnFooterOffsetY;

    protected boolean isLastPageFooter;

    protected boolean isReorderBandElements;

    protected int usedPageHeight = 0;

    /**
     *
     */
    protected JRBaseFiller(JasperReportsContext jasperReportsContext, JasperReportSource reportSource,
            BandReportFillerParent parent) throws JRException {
        super(jasperReportsContext, reportSource, parent);

        this.bandReportParent = parent;

        groups = mainDataset.groups;

        createReportTemplates(factory);

        String reportName = this.bandReportParent == null ? null : this.bandReportParent.getReportName();

        background = createFillBand(jasperReport.getBackground(), reportName, BandTypeEnum.BACKGROUND);
        title = createFillBand(jasperReport.getTitle(), reportName, BandTypeEnum.TITLE);
        pageHeader = createFillBand(jasperReport.getPageHeader(), reportName, BandTypeEnum.PAGE_HEADER);
        columnHeader = createFillBand(jasperReport.getColumnHeader(), reportName, BandTypeEnum.COLUMN_HEADER);

        detailSection = factory.getSection(jasperReport.getDetailSection());
        if (detailSection != missingFillSection) {
            detailSection.setOrigin(new JROrigin(reportName, BandTypeEnum.DETAIL));
        }

        columnFooter = createFillBand(jasperReport.getColumnFooter(), reportName, BandTypeEnum.COLUMN_FOOTER);
        pageFooter = createFillBand(jasperReport.getPageFooter(), reportName, BandTypeEnum.PAGE_FOOTER);
        lastPageFooter = createFillBand(jasperReport.getLastPageFooter(), reportName,
                BandTypeEnum.LAST_PAGE_FOOTER);

        summary = createFillBand(jasperReport.getSummary(), reportName, BandTypeEnum.SUMMARY);
        if (summary != missingFillBand && summary.isEmpty()) {
            summary = missingFillBand;
        }

        noData = createFillBand(jasperReport.getNoData(), reportName, BandTypeEnum.NO_DATA);

        initDatasets();
        initBands();
    }

    @Override
    protected void jasperReportSet() {
        SectionTypeEnum sectionType = jasperReport.getSectionType();
        if (sectionType != null && sectionType != SectionTypeEnum.BAND) {
            throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_UNSUPPORTED_REPORT_SECTION_TYPE,
                    new Object[] { jasperReport.getSectionType() });
        }

        /*   */
        name = jasperReport.getName();
        columnCount = jasperReport.getColumnCount();
        printOrder = jasperReport.getPrintOrderValue();
        columnDirection = jasperReport.getColumnDirection();
        pageWidth = jasperReport.getPageWidth();
        pageHeight = jasperReport.getPageHeight();
        orientation = jasperReport.getOrientationValue();
        whenNoDataType = jasperReport.getWhenNoDataTypeValue();
        columnWidth = jasperReport.getColumnWidth();
        columnSpacing = jasperReport.getColumnSpacing();
        leftMargin = jasperReport.getLeftMargin();
        rightMargin = jasperReport.getRightMargin();
        topMargin = jasperReport.getTopMargin();
        bottomMargin = jasperReport.getBottomMargin();
        isTitleNewPage = jasperReport.isTitleNewPage();
        isSummaryNewPage = jasperReport.isSummaryNewPage();
        isSummaryWithPageHeaderAndFooter = jasperReport.isSummaryWithPageHeaderAndFooter();
        isFloatColumnFooter = jasperReport.isFloatColumnFooter();
        whenResourceMissingType = jasperReport.getWhenResourceMissingTypeValue();
    }

    @Override
    protected JRFillObjectFactory initFillFactory() {
        JRFillObjectFactory fillFactory = new JRFillObjectFactory(this);

        // needed when creating group bands
        defaultStyleListeners = new ArrayList<DefaultStyleListener>();

        missingFillBand = new JRFillBand(this, null, fillFactory);
        missingFillSection = new JRFillSection(this, null, fillFactory);

        return fillFactory;
    }

    private JRFillBand createFillBand(JRBand reportBand, String reportName, BandTypeEnum bandType) {
        JRFillBand fillBand = factory.getBand(reportBand);
        if (fillBand != missingFillBand) {
            JROrigin origin = new JROrigin(reportName, bandType);
            fillBand.setOrigin(origin);
        }
        return fillBand;
    }

    @Override
    protected void setJasperReportsContext(JasperReportsContext jasperReportsContext) {
        super.setJasperReportsContext(jasperReportsContext);

        this.styleResolver = new StyleResolver(jasperReportsContext);
    }

    /**
     * Returns the report fields indexed by name.
     *
     * @return the report fields map
     */
    protected Map<String, JRFillField> getFieldsMap() {
        return mainDataset.fieldsMap;
    }

    /**
     * Returns the report variables indexed by name.
     *
     * @return the report variables map
     */
    protected Map<String, JRFillVariable> getVariablesMap() {
        return mainDataset.variablesMap;
    }

    /**
     * Returns a report field.
     *
     * @param fieldName the field name
     * @return the field
     */
    protected JRFillField getField(String fieldName) {
        return mainDataset.getFillField(fieldName);
    }

    private void initBands() {
        bands = new ArrayList<JRBand>(8 + (groups == null ? 0 : (2 * groups.length)));
        bands.add(title);
        bands.add(summary);
        bands.add(pageHeader);
        bands.add(pageFooter);
        bands.add(lastPageFooter);
        bands.add(columnHeader);
        bands.add(columnFooter);
        if (detailSection.getBands() != null) {
            bands.addAll(Arrays.asList(detailSection.getBands()));
        }
        bands.add(noData);

        if (groups != null && groups.length > 0) {
            for (int i = 0; i < groups.length; i++) {
                JRFillGroup group = groups[i];
                if (group.getGroupHeaderSection().getBands() != null) {
                    bands.addAll(Arrays.asList(group.getGroupHeaderSection().getBands()));
                }
                if (group.getGroupFooterSection().getBands() != null) {
                    bands.addAll(Arrays.asList(group.getGroupFooterSection().getBands()));
                }
            }
        }

        initBandsNowEvaluationTimes();
    }

    private void initBandsNowEvaluationTimes() {
        JREvaluationTime[] groupEvaluationTimes;
        if (groups == null) {
            groupEvaluationTimes = new JREvaluationTime[0];
        } else {
            groupEvaluationTimes = new JREvaluationTime[groups.length];
            for (int i = 0; i < groups.length; i++) {
                groupEvaluationTimes[i] = JREvaluationTime.getGroupEvaluationTime(groups[i].getName());
            }

            for (int i = 0; i < groups.length; i++) {
                JRGroup group = groups[i];
                JRFillSection footer = (JRFillSection) group.getGroupFooterSection();

                for (int j = i; j < groupEvaluationTimes.length; ++j) {
                    footer.addNowEvaluationTime(groupEvaluationTimes[j]);
                }
            }
        }

        columnFooter.addNowEvaluationTime(JREvaluationTime.EVALUATION_TIME_COLUMN);

        pageFooter.addNowEvaluationTime(JREvaluationTime.EVALUATION_TIME_COLUMN);
        pageFooter.addNowEvaluationTime(JREvaluationTime.EVALUATION_TIME_PAGE);

        summary.addNowEvaluationTimes(groupEvaluationTimes);
        noData.addNowEvaluationTimes(groupEvaluationTimes);
    }

    protected List<String> getPrintTransferPropertyPrefixes() {
        return printTransferPropertyPrefixes;
    }

    /**
     *
     */
    public JRStyledTextParser getStyledTextParser() {
        return styledTextParser;
    }

    protected JRStyledTextUtil getStyledTextUtil() {
        return fillContext.getStyledTextUtil();
    }

    /**
     * Returns the number of generated master print pages.
     * 
     * @return the number of generated master print pages
     */
    public int getCurrentPageCount() {
        return getMasterFiller().jasperPrint.getPages().size();
    }

    @Override
    public JRStyle getDefaultStyle() {
        return defaultStyle;
    }

    @Override
    public StyleResolver getStyleResolver() {
        return styleResolver;
    }

    protected boolean isSubreportRunToBottom() {
        return bandReportParent != null && bandReportParent.isRunToBottom();
    }

    /**
     *
     */
    protected JRPrintPage getCurrentPage() {
        return printPage;
    }

    /**
     *
     */
    protected abstract void setPageHeight(int pageHeight);

    @Override
    @continuable
    public JasperPrint fill(Map<String, Object> parameterValues) throws JRException {
        if (parameterValues == null) {
            parameterValues = new HashMap<String, Object>();
        }

        if (log.isDebugEnabled()) {
            log.debug("Fill " + fillerId + ": filling report");
        }

        setParametersToContext(parameterValues);

        fillingThread = Thread.currentThread();

        JRResourcesFillUtil.ResourcesFillContext resourcesContext = JRResourcesFillUtil
                .setResourcesFillContext(parameterValues);

        boolean success = false;
        try {
            createBoundElemementMaps();

            if (parent != null) {
                bandReportParent.registerSubfiller(this);
            }

            setParameters(parameterValues);

            setBookmarkHelper();

            loadStyles();

            jasperPrint.setName(name);
            jasperPrint.setPageWidth(pageWidth);
            jasperPrint.setPageHeight(pageHeight);
            jasperPrint.setTopMargin(topMargin);
            jasperPrint.setLeftMargin(leftMargin);
            jasperPrint.setBottomMargin(bottomMargin);
            jasperPrint.setRightMargin(rightMargin);
            jasperPrint.setOrientation(orientation);

            jasperPrint.setFormatFactoryClass(jasperReport.getFormatFactoryClass());
            jasperPrint.setLocaleCode(JRDataUtils.getLocaleCode(getLocale()));
            jasperPrint.setTimeZoneId(JRDataUtils.getTimeZoneId(getTimeZone()));

            propertiesUtil.transferProperties(mainDataset, jasperPrint,
                    JasperPrint.PROPERTIES_PRINT_TRANSFER_PREFIX);

            jasperPrint.setDefaultStyle(defaultStyle);

            /*   */
            if (styles != null && styles.length > 0) {
                for (int i = 0; i < styles.length; i++) {
                    addPrintStyle(styles[i]);
                }
            }

            /*   */
            mainDataset.start();

            /*   */
            fillReport();

            mainDataset.evaluateProperties(PropertyEvaluationTimeEnum.REPORT);

            propertiesUtil.transferProperties(mainDataset, jasperPrint,
                    JasperPrint.PROPERTIES_PRINT_TRANSFER_PREFIX);

            // add consolidates styles as normal styles in the print object
            //         for (Iterator it = consolidatedStyles.values().iterator(); it.hasNext();)
            //         {
            //            jasperPrint.addStyle((JRStyle) it.next());
            //         }

            if (log.isDebugEnabled()) {
                log.debug("Fill " + fillerId + ": ended");
            }

            success = true;
            return jasperPrint;
        } finally {
            mainDataset.closeDatasource();
            mainDataset.disposeParameterContributors();

            if (success && parent == null) {
                // commit the cached data
                fillContext.cacheDone();
            }

            if (parent != null) {
                bandReportParent.unregisterSubfiller(this);
            }

            delayedActions.dispose();

            fillingThread = null;

            //kill the subreport filler threads
            abortSubfillers();

            if (parent == null) {
                fillContext.dispose();
            }

            JRResourcesFillUtil.revertResourcesFillContext(resourcesContext);
        }
    }

    public void addPrintStyle(JRStyle style) throws JRException {
        jasperPrint.addStyle(style, true);
    }

    protected static interface DefaultStyleListener {
        void defaultStyleSet(JRStyle style);
    }

    private List<DefaultStyleListener> defaultStyleListeners;

    protected void addDefaultStyleListener(DefaultStyleListener listener) {
        defaultStyleListeners.add(listener);
    }

    protected void setDefaultStyle(JRStyle style) {
        defaultStyle = style;

        for (Iterator<DefaultStyleListener> it = defaultStyleListeners.iterator(); it.hasNext();) {
            DefaultStyleListener listener = it.next();
            listener.defaultStyleSet(style);
        }
    }

    protected void loadStyles() throws JRException {
        List<JRStyle> styleList = collectStyles();
        JRStyle reportDefaultStyle = jasperReport.getDefaultStyle();
        if (reportDefaultStyle == null) {
            lookupExternalDefaultStyle(styleList);
        }

        List<JRStyle> includedStyles = factory.setStyles(styleList);
        if (bandReportParent != null) {
            bandReportParent.registerReportStyles(includedStyles);
        }

        styles = includedStyles.toArray(new JRStyle[includedStyles.size()]);

        if (reportDefaultStyle != null) {
            setDefaultStyle(factory.getStyle(reportDefaultStyle));
        }
    }

    public void registerReportStyles(UUID id, List<JRStyle> styles) {
        if (bandReportParent == null) {
            fillContext.registerReportStyles(jasperReport, id, styles);
        } else {
            String reportLocation = bandReportParent.getReportLocation();
            if (reportLocation != null) {
                fillContext.registerReportStyles(reportLocation, id, styles);
            }
        }

    }

    private static final JRStyleSetter DUMMY_STYLE_SETTER = new JRStyleSetter() {
        @Override
        public void setStyle(JRStyle style) {
        }

        @Override
        public void setStyleNameReference(String name) {
        }
    };

    protected List<JRStyle> collectStyles() throws JRException {
        List<JRStyle> styleList = collectTemplateStyles();

        JRStyle[] reportStyles = jasperReport.getStyles();
        if (reportStyles != null) {
            styles = new JRStyle[reportStyles.length];//FIXME remove this

            for (int i = 0; i < reportStyles.length; i++) {
                JRStyle style = reportStyles[i];
                styleList.add(style);

                //add dummy style requester so that report styles are always included
                //in the final list
                factory.registerDelayedStyleSetter(DUMMY_STYLE_SETTER, style.getName());
            }
        }

        return styleList;
    }

    protected void collectTemplates() throws JRException {
        templates = new ArrayList<>();

        if (reportTemplates != null) {
            for (JRFillReportTemplate reportTemplate : reportTemplates) {
                ReportTemplateSource template = reportTemplate.evaluate();
                if (template != null) {
                    templates.add(template);
                }
            }
        }

        Collection<JRTemplate> paramTemplates = (Collection<JRTemplate>) mainDataset
                .getParameterValue(JRParameter.REPORT_TEMPLATES, true);
        if (paramTemplates != null) {
            for (JRTemplate template : paramTemplates) {
                templates.add(ReportTemplateSource.of(template));
            }
        }
    }

    public List<JRTemplate> getTemplates() {
        return templates.stream().map(source -> source.getTemplate()).collect(Collectors.toList());
    }

    protected List<JRStyle> collectTemplateStyles() throws JRException {
        collectTemplates();

        List<JRStyle> externalStyles = new ArrayList<JRStyle>();
        HashSet<String> loadedLocations = new HashSet<String>();
        for (ReportTemplateSource template : templates) {
            collectStyles(template, externalStyles, loadedLocations);
        }
        return externalStyles;
    }

    protected void collectStyles(ReportTemplateSource template, List<JRStyle> externalStyles,
            Set<String> loadedLocations) throws JRException {
        HashSet<String> parentLocations = new HashSet<String>();
        collectStyles(template, externalStyles, loadedLocations, parentLocations);
    }

    protected void collectStyles(ReportTemplateSource templateSource, List<JRStyle> externalStyles,
            Set<String> loadedLocations, Set<String> templateParentLocations) throws JRException {
        String templateLocation = templateSource.getTemplateResourceInfo() == null ? null
                : templateSource.getTemplateResourceInfo().getRepositoryResourceLocation();
        if (templateLocation != null && !templateParentLocations.add(templateLocation)) {
            throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_CIRCULAR_DEPENDENCY_FOUND,
                    new Object[] { templateLocation });
        }

        if (templateLocation != null && !loadedLocations.add(templateLocation)) {
            //already loaded
            return;
        }

        collectIncludedTemplates(templateSource, externalStyles, loadedLocations, templateParentLocations);

        JRStyle[] templateStyles = templateSource.getTemplate().getStyles();
        if (templateStyles != null) {
            for (int i = 0; i < templateStyles.length; i++) {
                JRStyle style = templateStyles[i];
                String styleName = style.getName();
                if (styleName == null) {
                    throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_EXTERNAL_STYLE_NAME_NOT_SET,
                            (Object[]) null);
                }

                externalStyles.add(style);
            }
        }
    }

    protected void collectIncludedTemplates(ReportTemplateSource templateSource, List<JRStyle> externalStyles,
            Set<String> loadedLocations, Set<String> templateParentLocations) throws JRException {
        JRTemplateReference[] includedTemplates = templateSource.getTemplate().getIncludedTemplates();
        if (includedTemplates != null && includedTemplates.length > 0) {
            RepositoryResourceContext currentContext = repositoryContext.getResourceContext();
            String contextLocation = templateSource.getTemplateResourceInfo() == null ? null
                    : templateSource.getTemplateResourceInfo().getRepositoryContextLocation();
            RepositoryResourceContext templateResourceContext = SimpleRepositoryResourceContext.of(contextLocation,
                    currentContext == null ? null : currentContext.getDerivedContextFallback());
            RepositoryContext templateRepositoryContext = SimpleRepositoryContext
                    .of(repositoryContext.getJasperReportsContext(), templateResourceContext);

            for (int i = 0; i < includedTemplates.length; i++) {
                JRTemplateReference reference = includedTemplates[i];
                String location = reference.getLocation();

                ReportTemplateSource includedTemplate = JRFillReportTemplate.loadTemplate(location, this,
                        templateRepositoryContext);
                collectStyles(includedTemplate, externalStyles, loadedLocations, templateParentLocations);
            }
        }
    }

    protected void lookupExternalDefaultStyle(Collection<JRStyle> styleList) {
        JRStyle defStyle = null;
        for (Iterator<JRStyle> it = styleList.iterator(); it.hasNext();) {
            JRStyle style = it.next();
            if (style.isDefault()) {
                defStyle = style;
            }
        }

        if (defStyle != null) {
            factory.registerDelayedStyleSetter(new JRStyleSetter() {
                @Override
                public void setStyle(JRStyle style) {
                    if (style.isDefault()) {
                        setDefaultStyle(style);
                    }
                }

                @Override
                public void setStyleNameReference(String name) {
                }
            }, defStyle.getName());
        }
    }

    private void createBoundElemementMaps() {
        createBoundElementMaps(JREvaluationTime.EVALUATION_TIME_MASTER);
        createBoundElementMaps(JREvaluationTime.EVALUATION_TIME_REPORT);
        createBoundElementMaps(JREvaluationTime.EVALUATION_TIME_PAGE);
        createBoundElementMaps(JREvaluationTime.EVALUATION_TIME_COLUMN);

        if (groups != null) {
            for (int i = 0; i < groups.length; i++) {
                createBoundElementMaps(JREvaluationTime.getGroupEvaluationTime(groups[i].getName()));
            }
        }

        for (Iterator<JRBand> i = bands.iterator(); i.hasNext();) {
            JRFillBand band = (JRFillBand) i.next();
            createBoundElementMaps(JREvaluationTime.getBandEvaluationTime(band));
        }
    }

    private void abortSubfillers() {
        if (subfillers != null && !subfillers.isEmpty()) {
            for (JRBaseFiller subfiller : subfillers.values()) {
                if (subfiller.bandReportParent != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("Fill " + fillerId + ": Aborting subfiller " + subfiller.fillerId);
                    }

                    subfiller.bandReportParent.abortSubfiller(subfiller);
                }
            }
        }
    }

    /**
     *
     */
    @continuable
    protected abstract void fillReport() throws JRException;

    @Override
    protected void ignorePaginationSet(Map<String, Object> parameterValues) {
        if (ignorePagination) {
            isTitleNewPage = false;
            isSummaryNewPage = false;
            if (groups != null) {
                for (int i = 0; i < groups.length; i++) {
                    groups[i].setStartNewPage(false);
                    groups[i].setResetPageNumber(false);
                    groups[i].setStartNewColumn(false);
                }
            }

            if (isMasterReport() || !bandReportParent.isParentPagination())//subreport page height is already set by band master
            {
                int maxPageHeight = getMaxPageHeight(parameterValues);
                setPageHeight(maxPageHeight);
            }
        }

        maxPageWidth = getMaxPageWidth(parameterValues);
    }

    protected int getMaxPageHeight(Map<String, Object> parameterValues) {
        Integer maxPageHeightParam = (Integer) parameterValues.get(JRParameter.MAX_PAGE_HEIGHT);
        int maxPageHeight = maxPageHeightParam != null ? maxPageHeightParam : PAGE_HEIGHT_PAGINATION_IGNORED;
        if (maxPageHeight < pageHeight) {
            if (log.isDebugEnabled()) {
                log.debug("max page height " + maxPageHeight + " smaller than report page height " + pageHeight);
            }

            //use the report page height
            maxPageHeight = pageHeight;
        }

        if (log.isDebugEnabled()) {
            log.debug("max page height is " + maxPageHeight);
        }

        return maxPageHeight;
    }

    protected int getMaxPageWidth(Map<String, Object> parameterValues) {
        Integer maxPageWidthParam = (Integer) parameterValues.get(JRParameter.MAX_PAGE_WIDTH);
        int maxPageWidth = maxPageWidthParam != null ? maxPageWidthParam : PAGE_WIDTH_IGNORED;

        if (maxPageWidth < pageWidth) {
            if (log.isDebugEnabled()) {
                log.debug("max page width " + maxPageWidth + " smaller than report page width " + pageWidth);
            }

            //use the report page width
            maxPageWidth = pageWidth;
        }

        if (log.isDebugEnabled()) {
            log.debug("max page width is " + maxPageWidth);
        }

        return maxPageWidth;
    }

    protected void recordUsedPageHeight(int pageHeight) {
        if (pageHeight > usedPageHeight) {
            usedPageHeight = pageHeight;
        }
    }

    /**
     * Returns the report resource bundle.
     *
     * @return the report resource bundle
     */
    protected ResourceBundle getResourceBundle() {
        return mainDataset.resourceBundle;
    }

    /**
     *
     */
    protected WhenNoDataTypeEnum getWhenNoDataType() {
        WhenNoDataTypeEnum result = whenNoDataType;

        if (result == null) {
            result = WhenNoDataTypeEnum
                    .getByName(propertiesUtil.getProperty(mainDataset, JRReport.CONFIG_PROPERTY_WHEN_NO_DATA_TYPE));
        }

        return result;
    }

    /**
     *
     */
    public Format getDateFormat(String pattern) {
        return getDateFormat(pattern, null);
    }

    protected Format getDateFormat(String pattern, TimeZone timeZone) {
        Locale lc = getLocale();
        TimeZone tz = timeZone == null ? getTimeZone() : timeZone;// default to filler timezone
        String key = pattern + "|" + JRDataUtils.getLocaleCode(lc) + "|" + JRDataUtils.getTimeZoneId(tz);
        Format format = dateFormatCache.get(key);
        if (format == null) {
            format = getFormatFactory().createDateFormat(pattern, lc, tz);
            if (format != null) {
                dateFormatCache.put(key, format);
            }
        }
        return format;
    }

    /**
     *
     */
    public Format getNumberFormat(String pattern) {
        Locale lc = getLocale();
        String key = pattern + "|" + JRDataUtils.getLocaleCode(lc);
        Format format = numberFormatCache.get(key);
        if (format == null) {
            format = getFormatFactory().createNumberFormat(pattern, lc);
            if (format != null) {
                numberFormatCache.put(key, format);
            }
        }
        return format;
    }

    protected boolean hasMasterFormatFactory() {
        return !isSubreport() || getFormatFactory().getClass().getName()
                .equals(fillContext.getMasterFormatFactory().getClass().getName());
    }

    protected boolean hasMasterLocale() {
        return !isSubreport() || getLocale().equals(fillContext.getMasterLocale());
    }

    protected boolean hasMasterTimeZone() {
        return !isSubreport() || getTimeZone().equals(fillContext.getMasterTimeZone());
    }

    /**
     * Sets a parameter's value.
     *
     * @param parameterName the parameter name
     * @param value the value
     * @throws JRException
     */
    protected void setParameter(String parameterName, Object value) throws JRException {
        mainDataset.setParameter(parameterName, value);
    }

    /**
     * Sets a parameter's value.
     *
     * @param parameter the parameter
     * @param value the value
     * @throws JRException
     */
    protected void setParameter(JRFillParameter parameter, Object value) throws JRException {
        mainDataset.setParameter(parameter, value);
    }

    /**
     *
     */
    protected boolean next() throws JRException {
        isCrtRecordOnPage = false;
        isCrtRecordOnColumn = false;

        return mainDataset.next();
    }

    /**
     * Resolves elements which are to be evaluated at report level.
     */
    protected void resolveReportBoundElements() throws JRException {
        resolveBoundElements(JREvaluationTime.EVALUATION_TIME_REPORT, JRExpression.EVALUATION_DEFAULT);
    }

    /**
     * Resolves elements which are to be evaluated at page level.
     *
     * @param evaluation
     *            the evaluation type
     */
    protected void resolvePageBoundElements(byte evaluation) throws JRException {
        resolveBoundElements(JREvaluationTime.EVALUATION_TIME_PAGE, evaluation);
    }

    /**
     * Resolves elements which are to be evaluated at column level.
     *
     * @param evaluation
     *            the evaluation type
     */
    protected void resolveColumnBoundElements(byte evaluation) throws JRException {
        resolveBoundElements(JREvaluationTime.EVALUATION_TIME_COLUMN, evaluation);
    }

    /**
     * Resolves elements which are to be evaluated at group level.
     *
     * @param evaluation
     *            the evaluation type
     * @param isFinal
     */
    protected void resolveGroupBoundElements(byte evaluation, boolean isFinal) throws JRException {
        if (groups != null && groups.length > 0) {
            for (int i = 0; i < groups.length; i++) {
                JRFillGroup group = groups[i];

                if ((group.hasChanged() && group.isFooterPrinted()) || isFinal) {
                    String groupName = group.getName();

                    resolveBoundElements(JREvaluationTime.getGroupEvaluationTime(groupName), evaluation);
                }
            }
        }
    }

    protected JRPrintPage newPage() {
        JRPrintPage page;

        if (fillContext.isUsingVirtualizer()) {
            JRVirtualPrintPage virtualPage = new JRVirtualPrintPage(virtualizationContext);
            page = virtualPage;
        } else {
            page = new JRBasePrintPage();
        }

        return page;
    }

    /**
     * Resloves elements which are to be evaluated at band level.
     *
     * @param band
     *            the band
     * @param evaluation
     *            the evaluation type
     * @throws JRException
     */
    protected void resolveBandBoundElements(JRFillBand band, byte evaluation) throws JRException {
        resolveBoundElements(JREvaluationTime.getBandEvaluationTime(band), evaluation);
    }

    protected void registerSubfiller(JRBaseFiller subfiller) {
        if (subfillers == null) {
            subfillers = new ConcurrentHashMap<Integer, JRBaseFiller>(16, 0.75f, 1);
        }

        subfillers.put(subfiller.fillerId, subfiller);
    }

    protected void unregisterSubfiller(JRBaseFiller subfiller) {
        if (subfillers != null) {
            subfillers.remove(subfiller.fillerId);
        }
    }

    /**
     *
     */
    protected void fillBand(JRPrintBand band) {
        if (isReorderBandElements()) {
            List<JRPrintElement> elements = new ArrayList<JRPrintElement>();
            for (Iterator<JRPrintElement> it = band.iterateElements(); it.hasNext();) {
                JRPrintElement element = it.next();
                element.setX(element.getX() + offsetX);
                element.setY(element.getY() + offsetY);
                elements.add(element);
            }
            Collections.sort(elements, new JRYXComparator());//FIXME make singleton comparator; same for older comparator
            for (JRPrintElement element : elements) {
                printPage.addElement(element);
                recordUsedWidth(element);
            }
        } else {
            for (Iterator<JRPrintElement> it = band.iterateElements(); it.hasNext();) {
                JRPrintElement element = it.next();
                element.setX(element.getX() + offsetX);
                element.setY(element.getY() + offsetY);
                printPage.addElement(element);
                recordUsedWidth(element);
            }
        }
    }

    protected void recordUsedWidth(JRPrintElement element) {
        recordUsedPageWidth(element.getX() + element.getWidth() + rightMargin);
    }

    protected void addPage(JRPrintPage page) {
        if (!isSubreport()) {
            if (log.isDebugEnabled()) {
                log.debug("Fill " + fillerId + ": adding page " + (jasperPrint.getPages().size() + 1));
            }

            // notify that the previous page was generated
            int pageCount = jasperPrint.getPages().size();
            if (pageCount > 0 && fillListener != null) {
                fillListener.pageGenerated(jasperPrint, pageCount - 1);
            }

            addLastPageBookmarks();

            jasperPrint.addPage(page);
            fillContext.setPrintPage(page);
        }
    }

    @continuable
    protected void addPageToParent(final boolean ended) throws JRException {
        if (printPage == null) {
            return;
        }

        FillerPageAddedEvent pageAdded = new FillerPageAddedEvent() {
            @Override
            public JasperPrint getJasperPrint() {
                return jasperPrint;
            }

            @Override
            public JRPrintPage getPage() {
                return printPage;
            }

            @Override
            public boolean hasReportEnded() {
                return ended;
            }

            @Override
            public int getPageStretchHeight() {
                return offsetY + bottomMargin;
            }

            @Override
            public int getPageIndex() {
                Number pageNumber = (Number) calculator.getPageNumber().getValue();
                if (pageNumber == null)//this happens when whenNoDataType="BlankPage" //FIXMEBOOK maybe we should set the variable?
                {
                    return 0;
                }
                return pageNumber.intValue() - 1;
            }

            @Override
            public JRBaseFiller getFiller() {
                return JRBaseFiller.this;
            }

            @Override
            public DelayedFillActions getDelayedActions() {
                return delayedActions;
            }
        };

        //FIXMEBOOK use a fill listener instead of this?
        bandReportParent.addPage(pageAdded);
    }

    protected void setMasterPageVariables(int currentPageIndex, int totalPages) {
        JRFillVariable masterCurrentPage = getVariable(JRVariable.MASTER_CURRENT_PAGE);
        if (masterCurrentPage != null) {
            masterCurrentPage.setValue(currentPageIndex + 1);
        }

        JRFillVariable masterTotalPages = getVariable(JRVariable.MASTER_TOTAL_PAGES);
        if (masterTotalPages != null) {
            masterTotalPages.setValue(totalPages);
        }
    }

    protected WhenResourceMissingTypeEnum getWhenResourceMissingType() {
        return mainDataset.whenResourceMissingType;
    }

    protected boolean isBandOverFlowAllowed() {
        return bandOverFlowAllowed;
    }

    protected void setBandOverFlowAllowed(boolean splittableBand) {
        this.bandOverFlowAllowed = splittableBand;
    }

    protected boolean isReorderBandElements() {
        return isReorderBandElements;
    }

    protected void setReorderBandElements(boolean isReorderBandElements) {
        this.isReorderBandElements = isReorderBandElements;
    }

    protected int getMasterColumnCount() {
        FillerParent fillerParent = parent;
        int colCount = 1;

        while (fillerParent != null) {
            BaseReportFiller filler = fillerParent.getFiller();
            if (filler instanceof JRBaseFiller)//FIXMEBOOK
            {
                colCount *= ((JRBaseFiller) filler).columnCount;
            }
            fillerParent = filler.parent;
        }

        return colCount;
    }

    /**
     * Returns the top-level (master) filler object.
     * 
     * @return the master filler object
     */
    public BaseReportFiller getMasterFiller() {
        BaseReportFiller filler = this;
        while (filler.parent != null) {
            filler = filler.parent.getFiller();
        }
        return filler;
    }

    protected void addBoundElement(JRFillElement element, JRPrintElement printElement,
            EvaluationTimeEnum evaluationType, String groupName, JRFillBand band) {
        JRFillGroup group = groupName == null ? null : getGroup(groupName);
        addBoundElement(element, printElement, evaluationType, group, band);
    }

    protected void addBoundElement(JRFillElement element, JRPrintElement printElement,
            EvaluationTimeEnum evaluationType, JRGroup group, JRFillBand band) {
        JREvaluationTime evaluationTime = JREvaluationTime.getEvaluationTime(evaluationType, group, band);
        addBoundElement(element, printElement, evaluationTime);
    }

    protected void addBoundElement(JRFillElement element, JRPrintElement printElement,
            JREvaluationTime evaluationTime) {
        // the key contains the page and its index; the index is only stored so that we have it in resolveBoundElements
        int pageIndex = currentPageIndex();
        FillPageKey pageKey = new FillPageKey(printPage, pageIndex);

        addBoundElement(element, printElement, evaluationTime, pageKey);
    }

    protected int currentPageIndex() {
        int pageIndex = ((Number) calculator.getPageNumber().getValue()).intValue() - 1;
        return pageIndex;
    }

    protected void subreportPageFilled(JRPrintPage subreportPage) {
        FillPageKey subreportKey = new FillPageKey(subreportPage);

        // this method is only called when the parent is a band report
        JRBaseFiller parentFiller = (JRBaseFiller) parent.getFiller();
        //FIXMEBOOK the index is only correct when the parent is the master, see fillListener.pageUpdated
        int parentPageIndex = parentFiller.getJasperPrint().getPages().size() - 1;
        FillPageKey parentKey = new FillPageKey(parentFiller.printPage, parentPageIndex);

        // move all delayed elements from the subreport page to the master page
        moveBoundActions(subreportKey, parentKey);
        // move all master evaluations to the parent
        parent.getFiller().delayedActions.moveMasterEvaluations(delayedActions, parentKey);
    }

    protected void moveBoundActions(FillPageKey subreportKey, FillPageKey parentKey) {
        delayedActions.moveActions(subreportKey, parentKey);

        if (subfillers != null)//recursive
        {
            for (JRBaseFiller subfiller : subfillers.values()) {
                subfiller.moveBoundActions(subreportKey, parentKey);
            }
        }
    }

    @Override
    public boolean isPageFinal(int pageIdx) {
        JRPrintPage page = jasperPrint.getPages().get(pageIdx);
        return !hasBoundActions(page);
    }

    public boolean isPageFinal(JRPrintPage page) {
        return !hasBoundActions(page);
    }

    protected boolean hasBoundActions(JRPrintPage page) {
        boolean hasActions = delayedActions.hasDelayedActions(page);
        if (hasActions) {
            return true;
        }

        if (subfillers != null) {
            for (JRBaseFiller subfiller : subfillers.values()) {
                // recursive
                if (subfiller.hasBoundActions(page)) {
                    return true;
                }
            }
        }

        return false;
    }

    protected JRFillGroup getGroup(String groupName) {
        JRFillGroup group = null;
        if (groups != null) {
            for (int i = 0; i < groups.length; i++) {
                if (groups[i].getName().equals(groupName)) {
                    group = groups[i];
                    break;
                }
            }
        }

        if (group == null) {
            throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_NO_SUCH_GROUP, new Object[] { groupName });
        }
        return group;
    }

    /**
     *
     */
    protected JRFillGroup getKeepTogetherGroup() {
        Integer keepTogetherGroupLevel = null;

        if (groups != null) {
            // check to see if there is any group that needs to be kept together or does not have the required min details
            for (int i = 0; i < groups.length; i++) {
                JRFillGroup group = groups[i];
                if (group.getKeepTogetherElementRange() != null
                        && (group.isKeepTogether() || !group.hasMinDetails())) {
                    keepTogetherGroupLevel = i;
                    break;
                }
            }
        }

        if (keepTogetherGroupLevel != null || orphanGroupFooterDetailElementRange != null) {
            // if there was a group that was going to be moved, either because it had keepTogether or less than required min details,
            // or if there is an orphan to be moved, we need to check if the outer groups still meet their required min details or need 
            // to be moved themselves, possibly instead of the inner group

            int detailsToMove = Math.max(
                    keepTogetherGroupLevel == null ? 0 : groups[keepTogetherGroupLevel].getDetailsCount(),
                    orphanGroupFooterDetailElementRange == null ? 0 : 1);

            int lcMaxGrpIdx = keepTogetherGroupLevel == null ? groups.length : keepTogetherGroupLevel;

            for (int i = 0; i < lcMaxGrpIdx; i++) {
                JRFillGroup group = groups[i];
                if (group.getKeepTogetherElementRange() != null && !group.hasMinDetails(detailsToMove)) {
                    keepTogetherGroupLevel = i;
                    break;
                }
            }
        }

        return keepTogetherGroupLevel == null ? null : groups[keepTogetherGroupLevel];
    }

    //   protected JRStyle getConsolidatedStyle(String consolidatedStyleName)
    //   {
    //      return (JRStyle) consolidatedStyles.get(consolidatedStyleName);
    //   }
    //
    //
    //   protected void putConsolidatedStyle(JRStyle consolidatedStyle)
    //   {
    //      consolidatedStyles.put(consolidatedStyle.getName(), consolidatedStyle);
    //   }

    protected void createReportTemplates(JRFillObjectFactory factory) {
        JRReportTemplate[] templates = jasperReport.getTemplates();
        if (templates != null) {
            reportTemplates = new JRFillReportTemplate[templates.length];

            for (int i = 0; i < templates.length; i++) {
                JRReportTemplate template = templates[i];
                reportTemplates[i] = factory.getReportTemplate(template);
            }
        }
    }

    protected int getFillerId() {
        return fillerId;
    }

    protected PrintElementOriginator assignElementId(JRFillElement fillElement) {
        int id = getFillContext().generateFillElementId();
        DefaultPrintElementOriginator originator = new DefaultPrintElementOriginator(id);
        return originator;
    }
}