All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jasperreports.components.table.fill.TableReport Maven / Gradle / Ivy

There is a newer version: 6.21.3
Show newest version
/*
 * 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 .
 */
package net.sf.jasperreports.components.table.fill;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;

import net.sf.jasperreports.annotations.properties.Property;
import net.sf.jasperreports.annotations.properties.PropertyScope;
import net.sf.jasperreports.components.ComponentsExtensionsRegistryFactory;
import net.sf.jasperreports.components.headertoolbar.HeaderToolbarElement;
import net.sf.jasperreports.components.headertoolbar.HeaderToolbarElementUtils;
import net.sf.jasperreports.components.iconlabel.IconLabelComponent;
import net.sf.jasperreports.components.iconlabel.IconLabelComponentUtil;
import net.sf.jasperreports.components.sort.FieldFilter;
import net.sf.jasperreports.components.sort.FilterTypesEnum;
import net.sf.jasperreports.components.sort.SortElementHtmlHandler;
import net.sf.jasperreports.components.sort.actions.FilterCommand;
import net.sf.jasperreports.components.table.BaseCell;
import net.sf.jasperreports.components.table.BaseColumn;
import net.sf.jasperreports.components.table.Cell;
import net.sf.jasperreports.components.table.Column;
import net.sf.jasperreports.components.table.ColumnGroup;
import net.sf.jasperreports.components.table.ColumnVisitor;
import net.sf.jasperreports.components.table.TableComponent;
import net.sf.jasperreports.components.table.WhenNoDataTypeTableEnum;
import net.sf.jasperreports.components.table.util.TableUtil;
import net.sf.jasperreports.engine.CompositeDatasetFilter;
import net.sf.jasperreports.engine.DatasetFilter;
import net.sf.jasperreports.engine.DatasetPropertyExpression;
import net.sf.jasperreports.engine.JRBand;
import net.sf.jasperreports.engine.JRChild;
import net.sf.jasperreports.engine.JRComponentElement;
import net.sf.jasperreports.engine.JRDataset;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRElementGroup;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionChunk;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRGroup;
import net.sf.jasperreports.engine.JROrigin;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRPropertiesHolder;
import net.sf.jasperreports.engine.JRPropertiesMap;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRQuery;
import net.sf.jasperreports.engine.JRReport;
import net.sf.jasperreports.engine.JRReportTemplate;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRScriptlet;
import net.sf.jasperreports.engine.JRSection;
import net.sf.jasperreports.engine.JRSortField;
import net.sf.jasperreports.engine.JRStaticText;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.JRTextField;
import net.sf.jasperreports.engine.JRValueParameter;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.base.JRBaseElement;
import net.sf.jasperreports.engine.base.JRBaseTextElement;
import net.sf.jasperreports.engine.component.FillContext;
import net.sf.jasperreports.engine.design.JRDesignBand;
import net.sf.jasperreports.engine.design.JRDesignElementGroup;
import net.sf.jasperreports.engine.design.JRDesignExpression;
import net.sf.jasperreports.engine.design.JRDesignFrame;
import net.sf.jasperreports.engine.design.JRDesignGenericElement;
import net.sf.jasperreports.engine.design.JRDesignGenericElementParameter;
import net.sf.jasperreports.engine.design.JRDesignGroup;
import net.sf.jasperreports.engine.design.JRDesignSection;
import net.sf.jasperreports.engine.design.JRDesignTextField;
import net.sf.jasperreports.engine.export.HtmlExporter;
import net.sf.jasperreports.engine.export.JRPdfExporterTagHelper;
import net.sf.jasperreports.engine.export.MatcherExporterFilter;
import net.sf.jasperreports.engine.fill.DatasetExpressionEvaluator;
import net.sf.jasperreports.engine.fill.JRExpressionEvalException;
import net.sf.jasperreports.engine.fill.JRFillField;
import net.sf.jasperreports.engine.fill.JRFillParameter;
import net.sf.jasperreports.engine.fill.JRFillVariable;
import net.sf.jasperreports.engine.type.BandTypeEnum;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.type.OrientationEnum;
import net.sf.jasperreports.engine.type.PositionTypeEnum;
import net.sf.jasperreports.engine.type.PrintOrderEnum;
import net.sf.jasperreports.engine.type.RunDirectionEnum;
import net.sf.jasperreports.engine.type.SectionTypeEnum;
import net.sf.jasperreports.engine.type.SortFieldTypeEnum;
import net.sf.jasperreports.engine.type.SortOrderEnum;
import net.sf.jasperreports.engine.type.SplitTypeEnum;
import net.sf.jasperreports.engine.type.StretchTypeEnum;
import net.sf.jasperreports.engine.type.WhenNoDataTypeEnum;
import net.sf.jasperreports.engine.type.WhenResourceMissingTypeEnum;
import net.sf.jasperreports.engine.util.DigestUtils;
import net.sf.jasperreports.engine.util.JRDataUtils;
import net.sf.jasperreports.engine.util.JRStringUtil;
import net.sf.jasperreports.engine.util.Pair;
import net.sf.jasperreports.engine.util.StyleResolver;
import net.sf.jasperreports.engine.util.StyleUtil;
import net.sf.jasperreports.properties.PropertyConstants;
import net.sf.jasperreports.web.util.JacksonUtil;

/**
 * @author Lucian Chirita ([email protected])
 */
public class TableReport implements JRReport
{
	public static final String EXCEPTION_MESSAGE_KEY_UNKNOWN_CHILD_TYPE = "fill.table.report.unknown.child.type";
	public static final String EXCEPTION_MESSAGE_KEY_FIELD_NOT_FOUND = "fill.table.report.field.not.found";
	public static final String EXCEPTION_MESSAGE_KEY_VARIABLE_NOT_FOUND = "fill.table.report.variable.not.found";
	public static final String EXCEPTION_MESSAGE_KEY_UNRECOGNIZED_FILTER_EXPRESSION_TYPE = "fill.table.report.unrecognized.filter.expression.type";
	
	public static final String METADATA_KEY_QUALIFICATION = 
			ComponentsExtensionsRegistryFactory.NAMESPACE 
			+ PropertyConstants.COMPONENT_KEY_QUALIFICATION_SEPARATOR 
			+ ComponentsExtensionsRegistryFactory.TABLE_COMPONENT_NAME;
	
	/**
	 * Global property that specifies the character to be used on the column header when the tables's column is sorted ascending
	 */
	@Property(
			category = PropertyConstants.CATEGORY_TABLE,
			defaultValue = "\\u25B2",
			scopes = {PropertyScope.CONTEXT},
			sinceVersion = PropertyConstants.VERSION_4_6_0
			)
	public static final String PROPERTY_UP_ARROW_CHAR = JRPropertiesUtil.PROPERTY_PREFIX + "components.sort.up.arrow.char"; //FIXMEJIVE move these from here

	/**
	 * Global property that specifies the character to be used on the column header when the tables's column is sorted descending
	 */
	@Property(
			category = PropertyConstants.CATEGORY_TABLE,
			defaultValue = "\\u25BC",
			scopes = {PropertyScope.CONTEXT},
			sinceVersion = PropertyConstants.VERSION_4_6_0
			)
	public static final String PROPERTY_DOWN_ARROW_CHAR = JRPropertiesUtil.PROPERTY_PREFIX + "components.sort.down.arrow.char";

	/**
	 * Global property that specifies the character to be used on the column header when the tables's column has a filtered applied
	 */
	@Property(
			category = PropertyConstants.CATEGORY_TABLE,
			defaultValue = "\\2606",
			scopes = {PropertyScope.CONTEXT},
			sinceVersion = PropertyConstants.VERSION_4_6_0
			)
	public static final String PROPERTY_FILTER_CHAR = JRPropertiesUtil.PROPERTY_PREFIX + "components.filter.char";

	/**
	 * Global property that specifies the font to be used for the icons on the column header
	 */
	@Property(
			category = PropertyConstants.CATEGORY_TABLE,
			scopes = {PropertyScope.CONTEXT},
			sinceVersion = PropertyConstants.VERSION_5_5_0
			)
	public static final String PROPERTY_ICON_FONT = JRPropertiesUtil.PROPERTY_PREFIX + "components.icon.font";

	/**
	 * Property that enables/disables the interactivity in the table component
	 * 
	 * 

* The property can be set: *

    *
  • globally
  • *
  • at report level
  • *
  • at component level
  • *
  • at column level
  • *
* *

* The default global value of this property is true */ @Property( category = PropertyConstants.CATEGORY_TABLE, defaultValue = PropertyConstants.BOOLEAN_TRUE, scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.COMPONENT, PropertyScope.TABLE_COLUMN}, scopeQualifications = {METADATA_KEY_QUALIFICATION}, sinceVersion = PropertyConstants.VERSION_4_7_1, valueType = Boolean.class ) public static final String PROPERTY_INTERACTIVE_TABLE = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.interactive"; /** * Property that enables/disables the floating header in the table component when scrolling. * If the interactivity has been disabled by setting {@link #PROPERTY_INTERACTIVE_TABLE} to false, then * setting this property will have no effect. * *

* It can be set: *

    *
  • globally
  • *
  • at report level
  • *
  • at component level
  • *
*

* *

* The default global value of this property is true *

*/ @Property( category = PropertyConstants.CATEGORY_TABLE, defaultValue = PropertyConstants.BOOLEAN_TRUE, scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.COMPONENT}, scopeQualifications = {METADATA_KEY_QUALIFICATION}, sinceVersion = PropertyConstants.VERSION_6_3_1, valueType = Boolean.class ) public static final String PROPERTY_FLOATING_HEADER = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.floating.header"; /** * Property that enables/disables the automatic addition of specific custom properties to table cell elements, * that would in turn trigger the creation of special document accessibility tags during PDF export * *

* The property can be set: *

    *
  • globally
  • *
  • at report level
  • *
  • at component level
  • *
* *

* The default global value of this property is false */ @Property( category = PropertyConstants.CATEGORY_TABLE, defaultValue = PropertyConstants.BOOLEAN_FALSE, scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.COMPONENT}, scopeQualifications = {METADATA_KEY_QUALIFICATION}, sinceVersion = PropertyConstants.VERSION_6_1_2, valueType = Boolean.class ) public static final String PROPERTY_GENERATE_TABLE_PDF_TAGS = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.generate.pdf.tags"; /** * Column property that specifies the field to be used for sorting, filtering and conditional formatting */ @Property( category = PropertyConstants.CATEGORY_TABLE, scopes = {PropertyScope.TABLE_COLUMN}, sinceVersion = PropertyConstants.VERSION_5_0_1 ) public static final String PROPERTY_COLUMN_FIELD = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.column.field"; /** * Column property that specifies the variable to be used for sorting, filtering and conditional formatting */ @Property( category = PropertyConstants.CATEGORY_TABLE, scopes = {PropertyScope.TABLE_COLUMN}, sinceVersion = PropertyConstants.VERSION_5_0_1 ) public static final String PROPERTY_COLUMN_VARIABLE = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.column.variable"; /** * Column property that enables/disables sorting * *

* It defaults to true */ @Property( category = PropertyConstants.CATEGORY_TABLE, defaultValue = PropertyConstants.BOOLEAN_TRUE, scopes = {PropertyScope.TABLE_COLUMN}, sinceVersion = PropertyConstants.VERSION_5_0_1, valueType = Boolean.class ) public static final String PROPERTY_COLUMN_SORTABLE = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.column.sortable"; /** * Column property that enables/disables filtering * *

* It defaults to true */ @Property( category = PropertyConstants.CATEGORY_TABLE, defaultValue = PropertyConstants.BOOLEAN_TRUE, scopes = {PropertyScope.TABLE_COLUMN}, sinceVersion = PropertyConstants.VERSION_5_0_1, valueType = Boolean.class ) public static final String PROPERTY_COLUMN_FILTERABLE = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.column.filterable"; /** * Column property that enables/disables conditional formatting * *

* It defaults to true * @deprecated To be removed. */ @Property( category = PropertyConstants.CATEGORY_TABLE, defaultValue = PropertyConstants.BOOLEAN_TRUE, scopes = {PropertyScope.TABLE_COLUMN}, sinceVersion = PropertyConstants.VERSION_5_0_1, valueType = Boolean.class ) public static final String PROPERTY_COLUMN_CONDITIONALLY_FORMATTABLE = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.column.conditionally.formattable"; /** * Property that provides a name for table. * *

* The name is used as report name in element origins. *

* @see JROrigin#getReportName() */ @Property( category = PropertyConstants.CATEGORY_TABLE, scopes = {PropertyScope.COMPONENT}, scopeQualifications = {METADATA_KEY_QUALIFICATION}, sinceVersion = PropertyConstants.VERSION_6_6_0 ) public static final String PROPERTY_TABLE_NAME = JRPropertiesUtil.PROPERTY_PREFIX + "components.table.name"; protected static final String SUMMARY_GROUP_NAME = "__SummaryGroup"; protected static final String HTML_CLASS_CELL_PREFIX = "cel_"; protected static final String HTML_CLASS_CELL = "jrcel"; /** * */ public static final String TABLE_HEADER_LABEL_MATCHER_EXPORT_KEY = "net.sf.jasperreports.components.table.header.label"; public static final String TABLE_HEADER_ICON_LABEL_MATCHER_EXPORT_KEY = "net.sf.jasperreports.components.table.header.icon.label"; private final FillContext fillContext; private final TableComponent table; private final JasperReport parentReport; private final TableReportDataset mainDataset; private final BuiltinExpressionEvaluatorFactory builtinEvaluatorFactory; private final JRSection detail; private final JRDesignBand title; private final JRDesignBand summary; private final JRDesignBand columnHeader; private final JRDesignBand pageFooter; private final JRDesignBand lastPageFooter; private final JRDesignBand noData; private final List tableIndexProperties; private final Map headerHtmlBaseProperties; private final JRPropertiesUtil propertiesUtil; private String tableName; private boolean isInteractiveTable; private boolean hasFloatingHeader; private boolean isGeneratePdfTags; private Map> columnInteractivityMapping; public TableReport( FillContext fillContext, TableComponent table, TableReportDataset mainDataset, List fillColumns, BuiltinExpressionEvaluatorFactory builtinEvaluatorFactory ) { this.tableIndexProperties = new ArrayList(); this.headerHtmlBaseProperties = new HashMap(); this.fillContext = fillContext; this.table = table; this.parentReport = fillContext.getFiller().getJasperReport(); this.mainDataset = mainDataset; this.builtinEvaluatorFactory = builtinEvaluatorFactory; this.tableName = JRPropertiesUtil.getOwnProperty(fillContext.getComponentElement(), PROPERTY_TABLE_NAME); this.propertiesUtil = JRPropertiesUtil.getInstance(fillContext.getFiller().getJasperReportsContext()); // begin: table interactivity this.isInteractiveTable = Boolean.valueOf(propertiesUtil.getProperty(PROPERTY_INTERACTIVE_TABLE, fillContext.getComponentElement(), this.parentReport)); this.columnInteractivityMapping = new HashMap>(); int interactiveColumnCount = 0; for (BaseColumn column: TableUtil.getAllColumns(table)) { boolean interactiveColumn = isInteractiveTable; String columnName = null; if (column.getPropertiesMap().containsProperty(PROPERTY_INTERACTIVE_TABLE)) { interactiveColumn = Boolean.valueOf(column.getPropertiesMap().getProperty(PROPERTY_INTERACTIVE_TABLE)); } if (interactiveColumn) { interactiveColumnCount++; } if (column.getPropertiesMap().containsProperty(JRComponentElement.PROPERTY_COMPONENT_NAME)) { columnName = column.getPropertiesMap().getProperty(JRComponentElement.PROPERTY_COMPONENT_NAME); } columnInteractivityMapping.put((Column)column, new Pair(interactiveColumn, columnName)); } if (interactiveColumnCount > 0) { this.isInteractiveTable = true; this.hasFloatingHeader = propertiesUtil.getBooleanProperty(PROPERTY_FLOATING_HEADER, true, fillContext.getComponentElement(), this.parentReport); } // end: table interactivity this.isGeneratePdfTags = Boolean.valueOf(propertiesUtil.getProperty(PROPERTY_GENERATE_TABLE_PDF_TAGS, fillContext.getComponentElement(), this.parentReport)); this.columnHeader = createColumnHeader(fillColumns); this.detail = wrapBand(createDetailBand(fillColumns), new JROrigin(BandTypeEnum.DETAIL)); this.title = createTitle(fillColumns); this.summary = createSummary(fillColumns); this.pageFooter = createPageFooter(fillColumns); setGroupBands(fillColumns); if (pageFooter != null && summary != null) { // if the table has both column footers and table footers, we need to use // a dummy group's footer to print the last column footers so that they // appear before the table footers addSummaryGroup(fillColumns); // use an empty last page footer so that the regular page footer doesn't // show on the last page this.lastPageFooter = new JRDesignBand(); this.lastPageFooter.setHeight(0); } else { // use the regular page footer this.lastPageFooter = null; } this.noData = createNoData(table.getNoData()); } public String getTableName() { return tableName; } protected class ReportBandInfo { final JRDesignBand band; final String bandId; final List rows = new ArrayList(); ReportBandInfo(JRDesignBand band, String bandId) { this.band = band; this.bandId = bandId; } protected void cellAdded(int rowLevel, CellInfo cell) { JRElement element = cell.getElement(); if (band.getHeight() < element.getHeight() + element.getY()) { band.setHeight(element.getHeight() + element.getY()); } BandRowInfo row = getBandRowInfo(rowLevel); row.addCell(cell); } JRDesignElementGroup getRowElementGroup(int rowLevel) { BandRowInfo row = getBandRowInfo(rowLevel); JRDesignElementGroup elementGroup = row.getElementGroup(); if (elementGroup == null) { elementGroup = new JRDesignElementGroup(); row.setElementGroup(elementGroup); band.addElementGroup(elementGroup); } return elementGroup; } BandRowInfo getBandRowInfo(int rowLevel) { int rowCount = rows.size(); if (rowLevel >= rowCount) { for (int level = rowCount; level <= rowLevel; ++level) { rows.add(new BandRowInfo()); } } return rows.get(rowLevel); } List getRows() { return rows; } String getBandId() { return bandId; } } protected class BandRowInfo { JRDesignElementGroup elementGroup; List cells = new ArrayList(); BandRowInfo() { } protected JRDesignElementGroup getElementGroup() { return elementGroup; } protected void setElementGroup(JRDesignElementGroup elementGroup) { this.elementGroup = elementGroup; } protected List getCells() { return cells; } protected void addCell(CellInfo cell) { cells.add(cell); } } protected class CellInfo { final JRElement element; final int rowSpan; final int colSpan; CellInfo(JRElement element, int rowSpan, int colSpan) { this.element = element; this.rowSpan = rowSpan; this.colSpan = colSpan; } protected JRElement getElement() { return element; } protected int getRowSpan() { return rowSpan; } protected int getColSpan() { return colSpan; } } protected abstract class ReportBandCreator implements ColumnVisitor { final ReportBandInfo bandInfo; final FillColumn fillColumn; int xOffset; int yOffset; int level; public ReportBandCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { this.bandInfo = bandInfo; this.fillColumn = fillColumn; this.xOffset = xOffset; this.yOffset = yOffset; this.level = level; } protected boolean isEmpty(Cell cell) { return cell == null; } @Override public Void visitColumn(Column column) { Cell cell = columnCell(column); if (!isEmpty(cell)) { int rowSpan = cell.getRowSpan() == null ? 1 : cell.getRowSpan(); int rowLevel = level + rowSpan - 1; JRDesignElementGroup elementGroup = bandInfo.getRowElementGroup(rowLevel); JRElement cellElement = createColumnCell(column, elementGroup, cell); elementGroup.addElement(cellElement); bandInfo.cellAdded(level, new CellInfo(cellElement, rowSpan, fillColumn.getColSpan())); yOffset += cell.getHeight(); } xOffset += column.getWidth(); return null; } protected UUID deriveUUID() { UUID columnUUID = fillColumn.getTableColumn().getUUID(); UUID derived = DigestUtils.instance().deriveUUID(columnUUID, bandInfo.getBandId()); return derived; } protected abstract Cell columnCell(Column column); protected JRElement createColumnCell(Column column, JRElementGroup parentGroup, Cell cell) { return createColumnCell(column, parentGroup, cell, false); } protected JRElement createColumnCell(Column column, JRElementGroup parentGroup, Cell cell, boolean forceFrame) { return createCell(parentGroup, cell, column.getWidth(), fillColumn.getWidth(), xOffset, yOffset, column.hashCode(), deriveUUID(), forceFrame); } @Override public Void visitColumnGroup(ColumnGroup columnGroup) { Cell cell = columnGroupCell(columnGroup); int cellHeight = 0; int sublevel = level; if (cell != null) { int rowSpan = cell.getRowSpan() == null ? 1 : cell.getRowSpan(); int rowLevel = level + rowSpan - 1; int colSpan = 0; for (FillColumn subcolumn : fillColumn.getSubcolumns()) { colSpan += subcolumn.getColSpan(); } colSpan = colSpan == 0 ? 1 : colSpan; JRDesignElementGroup elementGroup = bandInfo.getRowElementGroup(rowLevel); JRElement cellElement = createColumnGroupCell(columnGroup, cell, elementGroup); elementGroup.addElement(cellElement); bandInfo.cellAdded(level, new CellInfo(cellElement, rowSpan, colSpan)); cellHeight = cell.getHeight(); sublevel += rowSpan; } for (FillColumn subcolumn : fillColumn.getSubcolumns()) { ReportBandCreator subVisitor = createSubVisitor(subcolumn, xOffset, yOffset + cellHeight, sublevel); subVisitor.visit(); xOffset = subVisitor.xOffset; } return null; } protected JRElement createColumnGroupCell(ColumnGroup columnGroup, Cell cell, JRDesignElementGroup elementGroup) { return createCell(elementGroup, cell, columnGroup.getWidth(), fillColumn.getWidth(), xOffset, yOffset, null, deriveUUID(), false); } protected abstract Cell columnGroupCell(ColumnGroup group); protected abstract ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int subLevel); public void visit() { fillColumn.getTableColumn().visitColumn(this); } } protected abstract class ReverseReportBandCreator extends ReportBandCreator { public ReverseReportBandCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); } @Override public Void visitColumnGroup(ColumnGroup columnGroup) { Cell cell = columnGroupCell(columnGroup); int rowSpan; if (cell == null) { rowSpan = 0; } else if (cell.getRowSpan() == null) { rowSpan = 1; } else { rowSpan = cell.getRowSpan(); } int origXOffset = xOffset; int origYOffset = yOffset; int colSpan = 0; for (FillColumn subcolumn : fillColumn.getSubcolumns()) { ReportBandCreator subVisitor = createSubVisitor(subcolumn, xOffset, origYOffset, level + rowSpan); subVisitor.visit(); xOffset = subVisitor.xOffset; if (subVisitor.yOffset > yOffset) { yOffset = subVisitor.yOffset; } colSpan += subcolumn.getColSpan(); } colSpan = colSpan == 0 ? 1 : colSpan; if (cell != null) { int rowLevel = level + rowSpan - 1; JRDesignElementGroup elementGroup = bandInfo.getRowElementGroup(rowLevel); JRElement cellElement = createCell(elementGroup, cell, columnGroup.getWidth(), fillColumn.getWidth(), origXOffset, yOffset, null, deriveUUID(), false); elementGroup.addElement(cellElement); bandInfo.cellAdded(level, new CellInfo(cellElement, rowSpan, colSpan)); yOffset += cell.getHeight(); } return null; } } protected class DetailBandCreator extends ReportBandCreator { public DetailBandCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); } @Override protected Cell columnCell(Column column) { return column.getDetailCell(); } @Override protected Cell columnGroupCell(ColumnGroup group) { return null; } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new DetailBandCreator(bandInfo, subcolumn, xOffset, yOffset, sublevel); } @Override protected boolean isEmpty(Cell cell) { if (super.isEmpty(cell)) { return true; } // also threat zero height cells as empty. // FIXME only doing this for the detail cells to minimize impact, it should apply to all cells List children = cell.getChildren(); return cell.getHeight() == 0 && (children == null || children.isEmpty()); } } protected JRBand createDetailBand(List fillColumns) { final JRDesignBand detailBand = new JRDesignBand(); detailBand.setSplitType(SplitTypeEnum.PREVENT); detailBand.setPrintWhenExpression(table.getDetail() == null ? null : table.getDetail().getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(detailBand, BandTypeEnum.DETAIL.getName()); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { DetailBandCreator subVisitor = new DetailBandCreator( bandInfo, subcolumn, xOffset, 0, 0); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, false); return detailBand; } protected class ColumnHeaderCreator extends ReportBandCreator { private Map headerBaseProperties; private final AtomicBoolean firstColumn;// we need a mutable boolean reference public ColumnHeaderCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level, Map headerBaseProperties, AtomicBoolean firstColumn) { super(bandInfo, fillColumn, xOffset, yOffset, level); this.headerBaseProperties = headerBaseProperties; this.firstColumn = firstColumn; } @Override protected Cell columnCell(Column column) { return column.getColumnHeader(); } @Override protected JRDesignFrame createColumnCell(Column column, JRElementGroup parentGroup, Cell cell) { JRDesignFrame frame = (JRDesignFrame) createColumnCell(column, parentGroup, cell, true); addHeaderToolbarElement(column, frame, TableUtil.getCellElement(JRTextField.class, column.getDetailCell(), true)); return frame; } protected JRExpression getColumnHeaderLabelExpression(Cell header) { List detailElements = header == null ? null : header.getChildren(); // only consider cells with a single text fields if (detailElements == null || detailElements.size() != 1) { return null; } JRChild detailElement = detailElements.get(0); if (detailElement instanceof JRTextField) { return ((JRTextField) detailElement).getExpression(); } if (detailElement instanceof JRStaticText) { return builtinEvaluatorFactory.createConstantExpression(((JRStaticText)detailElement).getText()); } return null; } protected void addHeaderToolbarElement(Column column, JRDesignFrame frame, JRTextField sortTextField) { int columnIndex = TableUtil.getColumnIndex(column, table); Pair columnData = columnInteractivityMapping.get(column); boolean interactiveColumn = columnData.first(); if (sortTextField != null && interactiveColumn) { Cell header = column.getColumnHeader(); JRDesignGenericElement genericElement = new JRDesignGenericElement(header.getDefaultStyleProvider()); genericElement.setGenericType(HeaderToolbarElement.ELEMENT_TYPE); genericElement.setPositionType(net.sf.jasperreports.engine.type.PositionTypeEnum.FIX_RELATIVE_TO_TOP); genericElement.setX(0); genericElement.setY(0); // TODO lucianc setting to 1 for now; we can't set to frame size as we might not know the padding genericElement.setHeight(1); genericElement.setWidth(1); genericElement.setMode(ModeEnum.TRANSPARENT); //genericElement.setStretchType(StretchTypeEnum.RELATIVE_TO_BAND_HEIGHT); String fieldOrVariableName = null; SortFieldTypeEnum columnType = null; FilterTypesEnum filterType = null; TimeZone formatTimeZone = null;//FIXME also define via properties when sortTextField is not single chunk String suffix = ""; boolean hasFieldOrVariable = false; if (column.getPropertiesMap().containsProperty(PROPERTY_COLUMN_FIELD)) { hasFieldOrVariable = true; fieldOrVariableName = column.getPropertiesMap().getProperty(PROPERTY_COLUMN_FIELD); columnType = SortFieldTypeEnum.FIELD; JRField field = getField(fieldOrVariableName); if (field != null) { filterType = HeaderToolbarElementUtils.getFilterType(field.getValueClass()); } else { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_FIELD_NOT_FOUND, new Object[]{fieldOrVariableName} ); } } else if (column.getPropertiesMap().containsProperty(PROPERTY_COLUMN_VARIABLE)) { hasFieldOrVariable = true; fieldOrVariableName = column.getPropertiesMap().getProperty(PROPERTY_COLUMN_VARIABLE); columnType = SortFieldTypeEnum.VARIABLE; JRVariable variable = getVariable(fieldOrVariableName); if (variable != null) { filterType = HeaderToolbarElementUtils.getFilterType(variable.getValueClass()); } else { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_VARIABLE_NOT_FOUND, new Object[]{fieldOrVariableName} ); } } else if (TableUtil.hasSingleChunkExpression(sortTextField)) { JRExpressionChunk sortExpression = sortTextField.getExpression().getChunks()[0]; fieldOrVariableName = sortExpression.getText(); switch (sortExpression.getType()) { case JRExpressionChunk.TYPE_FIELD: columnType = SortFieldTypeEnum.FIELD; JRField field = getField(fieldOrVariableName); filterType = HeaderToolbarElementUtils.getFilterType(field.getValueClass()); formatTimeZone = getFormatTimeZone(sortTextField, field.getValueClass()); break; case JRExpressionChunk.TYPE_VARIABLE: columnType = SortFieldTypeEnum.VARIABLE; JRVariable variable = getVariable(fieldOrVariableName); filterType = HeaderToolbarElementUtils.getFilterType(variable.getValueClass()); formatTimeZone = getFormatTimeZone(sortTextField, variable.getValueClass()); break; default: // never throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_UNRECOGNIZED_FILTER_EXPRESSION_TYPE, new Object[]{sortExpression.getType()} ); } } boolean isSortable = propertiesUtil.getBooleanProperty(column.getPropertiesMap(), PROPERTY_COLUMN_SORTABLE, true) && fieldOrVariableName != null; boolean isFilterable = propertiesUtil.getBooleanProperty(column.getPropertiesMap(), PROPERTY_COLUMN_FILTERABLE, true) && fieldOrVariableName != null && TableUtil.isFilterable(sortTextField) && filterType != null; if (isSortable) { // column is sortable genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_CAN_SORT, Boolean.TRUE.toString()); //FIXMEJIVE consider moving in separate method JRSortField[] sortFields = TableReport.this.mainDataset.getSortFields(); if (sortFields != null) { for(JRSortField sortField : sortFields) { if ( sortField.getName().equals(fieldOrVariableName) && sortField.getType() == columnType ) { suffix += "" + (sortField.getOrderValue() == SortOrderEnum.ASCENDING ? propertiesUtil.getProperty(PROPERTY_UP_ARROW_CHAR) : (sortField.getOrderValue() == SortOrderEnum.DESCENDING ? propertiesUtil.getProperty(PROPERTY_DOWN_ARROW_CHAR) : "")); } } } } else { // column is not sortable genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_CAN_SORT, Boolean.FALSE.toString()); } if (isFilterable) { // column is filterable genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_CAN_FILTER, Boolean.TRUE.toString()); JasperReportsContext jasperReportsContext = fillContext.getFiller().getJasperReportsContext(); String serializedFilters = TableReport.this.mainDataset.getPropertiesMap().getProperty(FilterCommand.DATASET_FILTER_PROPERTY); if (serializedFilters != null) { List existingFilters = JacksonUtil.getInstance(jasperReportsContext).loadList(serializedFilters, FieldFilter.class); if (existingFilters != null) { List fieldFilters = new ArrayList(); SortElementHtmlHandler.getFieldFilters(new CompositeDatasetFilter(existingFilters), fieldFilters, fieldOrVariableName); if (fieldFilters.size() > 0) { suffix += "" + propertiesUtil.getProperty(PROPERTY_FILTER_CHAR); } } } if (formatTimeZone != null && !formatTimeZone.equals(fillContext.getFiller().getFillContext().getMasterTimeZone())) { genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_FORMAT_TIME_ZONE, JRDataUtils.getTimeZoneId(formatTimeZone)); } } else { // column is not filterable genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_CAN_FILTER, Boolean.FALSE.toString()); } if (suffix.length() > 0) { addIconLabelComponent(column, frame, suffix); // HeaderLabelBuiltinExpression evaluator = HeaderLabelUtil.alterHeaderLabel(frame, suffix); // if (evaluator != null) // { // builtinEvaluators.put(evaluator.getExpression(), evaluator); // } } if (isSortable || isFilterable) { genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_TYPE, columnType.getName()); } if (filterType != null) { genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_FILTER_TYPE, filterType.getName()); } if (columnType != null && hasFieldOrVariable) { String property = SortFieldTypeEnum.FIELD.equals(columnType) ? HeaderToolbarElement.PROPERTY_COLUMN_FIELD : HeaderToolbarElement.PROPERTY_COLUMN_VARIABLE; genericElement.getPropertiesMap().setProperty(property, fieldOrVariableName); } String columnName = fieldOrVariableName != null ? fieldOrVariableName : String.valueOf(columnIndex); String columnUuid = column.getUUID().toString();//columnName + "_" + column.hashCode(); String cellId = columnName + "_" + column.hashCode(); if (firstColumn.compareAndSet(false, true)) { // only setting on the first column to save memory //FIXME a cleaner approach would be to set these another single generic element addColumnLabelParameters(genericElement, table); // setting component name on first column String tableName = propertiesUtil.getProperty(JRComponentElement.PROPERTY_COMPONENT_NAME, fillContext.getComponentElement()); genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_TABLE_NAME, tableName); // set the hasFloatingHeader property on first column genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_FLOATING_HEADER, String.valueOf(hasFloatingHeader)); } genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_UUID, columnUuid); genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_INDEX, Integer.toString(columnIndex)); genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_NAME, columnName); genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_COMPONENT_NAME, columnData.second()); genericElement.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_TABLE_UUID, fillContext.getComponentElement().getUUID().toString()); addElementParameter(genericElement, HeaderToolbarElement.PARAMETER_COLUMN_LABEL, getColumnHeaderLabelExpression(header)); frame.getPropertiesMap().setProperty(HtmlExporter.PROPERTY_HTML_CLASS, "jrcolHeader" + (interactiveColumn ? " interactiveElement" : "")); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_UUID, columnUuid); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_TABLE_UUID, fillContext.getComponentElement().getUUID().toString()); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_INDEX, String.valueOf(columnIndex)); String cellIdFixedPart = JRStringUtil.getCSSClass(cellId + "_"); TableIndexProperties cellIdProperties = new TableIndexProperties( HeaderToolbarElement.PROPERTY_CELL_ID, cellIdFixedPart); tableIndexProperties.add(cellIdProperties); assert frame.getPropertiesMap().getBaseProperties() == null; frame.getPropertiesMap().setBaseProperties(cellIdProperties.getPropertiesMap()); String classFixedPart = TableReport.HTML_CLASS_CELL + " " + TableReport.HTML_CLASS_CELL_PREFIX + cellIdFixedPart; TableIndexProperties columnClassProperties = new TableIndexProperties( HtmlExporter.PROPERTY_HTML_CLASS, classFixedPart); tableIndexProperties.add(columnClassProperties); headerBaseProperties.put(column.hashCode(), columnClassProperties.getPropertiesMap()); frame.addElement(0, genericElement); } else if (isInteractiveTable) { String columnName = String.valueOf(columnIndex); String cellId = columnName + "_" + column.hashCode(); frame.getPropertiesMap().setProperty(HtmlExporter.PROPERTY_HTML_CLASS, "jrcolHeader"); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_UUID, column.getUUID().toString()); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_CELL_ID, cellId); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_TABLE_UUID, fillContext.getComponentElement().getUUID().toString()); frame.getPropertiesMap().setProperty(HeaderToolbarElement.PROPERTY_COLUMN_INDEX, String.valueOf(columnIndex)); } } protected void addIconLabelComponent(Column column, JRDesignFrame frame, String suffix) { List children = frame.getChildren(); if (children.size() > 0) { JRBaseTextElement headerTextElement = (JRBaseTextElement)children.get(0); if (headerTextElement != null) { JRComponentElement componentElement = IconLabelComponentUtil.getInstance(fillContext.getFiller().getJasperReportsContext()).createIconLabelComponentElement(headerTextElement); IconLabelComponent iconLabelComponent = (IconLabelComponent)componentElement.getComponent(); JRDesignTextField labelTextField = (JRDesignTextField)iconLabelComponent.getLabelTextField(); if (headerTextElement instanceof JRTextField) { labelTextField.setExpression(((JRTextField) headerTextElement).getExpression()); } else if (headerTextElement instanceof JRStaticText) { labelTextField.setExpression(builtinEvaluatorFactory.createConstantExpression(((JRStaticText)headerTextElement).getText())); } JRDesignTextField iconTextField = (JRDesignTextField)iconLabelComponent.getIconTextField(); iconTextField.setExpression(builtinEvaluatorFactory.createConstantExpression(suffix)); componentElement.getPropertiesMap().setProperty(MatcherExporterFilter.PROPERTY_MATCHER_EXPORT_FILTER_KEY, TABLE_HEADER_ICON_LABEL_MATCHER_EXPORT_KEY); JRBaseElement element = (JRBaseElement)frame.getChildren().get(0); element.getPropertiesMap().setProperty(MatcherExporterFilter.PROPERTY_MATCHER_EXPORT_FILTER_KEY, TABLE_HEADER_LABEL_MATCHER_EXPORT_KEY); //frame.getChildren().remove(0); frame.getChildren().add(componentElement); } } } protected void addElementParameter(JRDesignGenericElement element, String name, Object value) { JRDesignGenericElementParameter param = new JRDesignGenericElementParameter(); param.setName(name); JRDesignExpression valueExpression = builtinEvaluatorFactory.createConstantExpression(value); param.setValueExpression(valueExpression); element.addParameter(param); } protected void addElementParameter(JRDesignGenericElement element, String name, JRExpression expression) { JRDesignGenericElementParameter param = new JRDesignGenericElementParameter(); param.setName(name); param.setValueExpression(expression); element.addParameter(param); } protected void addColumnLabelParameters(JRDesignGenericElement element, TableComponent table) { List columns = TableUtil.getAllColumns(table); for(int i = 0, ln = columns.size(); i < ln; i++) { BaseColumn column = columns.get(i); JRExpression columnHeaderExpression = getColumnHeaderLabelExpression(column.getColumnHeader()); boolean interactiveColumn = columnInteractivityMapping.get(column).first() && (TableUtil.getCellElement(JRTextField.class, ((Column)column).getDetailCell(), true) != null); String paramName = HeaderToolbarElement.PARAM_COLUMN_LABEL_PREFIX + i + "|" + column.getUUID().toString() + "|" + interactiveColumn; addElementParameter(element, paramName, columnHeaderExpression); } } @Override protected Cell columnGroupCell(ColumnGroup group) { return group.getColumnHeader(); } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new ColumnHeaderCreator(bandInfo, subcolumn, xOffset, yOffset, sublevel, headerHtmlBaseProperties, firstColumn); } @Override protected JRElement createColumnGroupCell(ColumnGroup columnGroup, Cell cell, JRDesignElementGroup elementGroup) { JRDesignFrame frame = (JRDesignFrame) createCell(elementGroup, cell, columnGroup.getWidth(), fillColumn.getWidth(), xOffset, yOffset, null, deriveUUID(), true); frame.getPropertiesMap().setProperty(HtmlExporter.PROPERTY_HTML_CLASS, "jrcolGroupHeader"); return frame; } } protected TimeZone getFormatTimeZone(JRTextField textField, Class valueClass) { //FIXME this duplicates the logic in JRFillTextField.determineOwnTimeZone() String formatTimeZone = null; if (textField.hasProperties()) { formatTimeZone = textField.getPropertiesMap().getProperty(JRTextField.PROPERTY_FORMAT_TIMEZONE); } if (formatTimeZone == null || formatTimeZone.isEmpty()) { if (java.sql.Date.class.isAssignableFrom(valueClass)) { formatTimeZone = propertiesUtil.getProperty(parentReport, JRTextField.PROPERTY_SQL_DATE_FORMAT_TIMEZONE); } else if (java.sql.Timestamp.class.isAssignableFrom(valueClass)) { formatTimeZone = propertiesUtil.getProperty(parentReport, JRTextField.PROPERTY_SQL_TIMESTAMP_FORMAT_TIMEZONE); } else if (java.sql.Time.class.isAssignableFrom(valueClass)) { formatTimeZone = propertiesUtil.getProperty(parentReport, JRTextField.PROPERTY_SQL_TIME_FORMAT_TIMEZONE); } } if (formatTimeZone == null || formatTimeZone.isEmpty()) { formatTimeZone = propertiesUtil.getProperty(parentReport, JRTextField.PROPERTY_FORMAT_TIMEZONE); } TimeZone reportTimeZone = fillContext.getFillDataset().getTimeZone(); return JRDataUtils.resolveFormatTimeZone(formatTimeZone, reportTimeZone); } protected JRDesignBand createColumnHeader(List fillColumns) { JRDesignBand columnHeader = new JRDesignBand(); columnHeader.setSplitType(SplitTypeEnum.PREVENT); columnHeader.setPrintWhenExpression(table.getColumnHeader() == null ? null : table.getColumnHeader().getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(columnHeader, BandTypeEnum.COLUMN_HEADER.getName()); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { ColumnHeaderCreator subVisitor = new ColumnHeaderCreator( bandInfo, subcolumn, xOffset, 0, 0, headerHtmlBaseProperties, new AtomicBoolean()); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, true); if (columnHeader.getHeight() == 0) { columnHeader = null; } return columnHeader; } protected class PageFooterCreator extends ReverseReportBandCreator { public PageFooterCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); } @Override protected Cell columnCell(Column column) { return column.getColumnFooter(); } @Override protected Cell columnGroupCell(ColumnGroup group) { return group.getColumnFooter(); } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new PageFooterCreator(bandInfo, subcolumn, xOffset, yOffset, sublevel); } } protected JRDesignBand createPageFooter(List fillColumns) { JRDesignBand pageFooter = new JRDesignBand(); pageFooter.setSplitType(SplitTypeEnum.PREVENT); pageFooter.setPrintWhenExpression(table.getColumnFooter() == null ? null : table.getColumnFooter().getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(pageFooter, BandTypeEnum.PAGE_FOOTER.getName()); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { PageFooterCreator subVisitor = new PageFooterCreator( bandInfo, subcolumn, xOffset, 0, 0); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, false); if (pageFooter.getHeight() == 0) { pageFooter = null; } return pageFooter; } protected JRDesignBand createNoData(BaseCell cell) { JRDesignBand noData = null; if (cell != null) { noData = new JRDesignBand(); noData.setSplitType(SplitTypeEnum.PREVENT); //noData.setPrintWhenExpression(table.getColumnFooter() == null ? null : table.getColumnFooter().getPrintWhenExpression()); noData.addElement( createCell( null, cell, fillContext.getComponentElement().getWidth(), fillContext.getComponentElement().getWidth(), 0, 0, null, null, false ) ); // if (noData.getHeight() == 0) // { // noData = null; // } } return noData; } protected class TitleCreator extends ReportBandCreator { public TitleCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); } @Override protected Cell columnCell(Column column) { return column.getTableHeader(); } @Override protected Cell columnGroupCell(ColumnGroup group) { return group.getTableHeader(); } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new TitleCreator(bandInfo, subcolumn, xOffset, yOffset, sublevel); } } protected JRDesignBand createTitle(List fillColumns) { JRDesignBand title = new JRDesignBand(); title.setSplitType(SplitTypeEnum.PREVENT); title.setPrintWhenExpression(table.getTableHeader() == null ? null : table.getTableHeader().getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(title, BandTypeEnum.TITLE.getName()); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { TitleCreator subVisitor = new TitleCreator( bandInfo, subcolumn, xOffset, 0, 0); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, false); if (title.getHeight() == 0) //FIXMETABLE not sure we actually need this; maybe check the section is truly empty; do the same for the other sections as well { title = null; } return title; } protected class SummaryCreator extends ReverseReportBandCreator { public SummaryCreator(ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); } @Override protected Cell columnCell(Column column) { return column.getTableFooter(); } @Override protected Cell columnGroupCell(ColumnGroup group) { return group.getTableFooter(); } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new SummaryCreator(bandInfo, subcolumn, xOffset, yOffset, sublevel); } } protected JRDesignBand createSummary(List fillColumns) { JRDesignBand summary = new JRDesignBand(); summary.setSplitType(SplitTypeEnum.PREVENT); summary.setPrintWhenExpression(table.getTableFooter() == null ? null : table.getTableFooter().getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(summary, BandTypeEnum.SUMMARY.getName()); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { SummaryCreator subVisitor = new SummaryCreator( bandInfo, subcolumn, xOffset, 0, 0); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, false); if (summary.getHeight() == 0) { summary = null; } return summary; } protected class GroupHeaderCreator extends ReportBandCreator { private final String groupName; public GroupHeaderCreator(String groupName, ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); this.groupName = groupName; } @Override protected Cell columnCell(Column column) { return column.getGroupHeader(groupName); } @Override protected Cell columnGroupCell(ColumnGroup group) { return group.getGroupHeader(groupName); } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new GroupHeaderCreator(groupName, bandInfo, subcolumn, xOffset, yOffset, sublevel); } } protected JRBand createGroupHeader(String groupName, List fillColumns) { JRDesignBand header = new JRDesignBand(); header.setSplitType(SplitTypeEnum.PREVENT); header.setPrintWhenExpression(table.getGroupHeader(groupName) == null ? null : table.getGroupHeader(groupName).getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(header, BandTypeEnum.GROUP_HEADER + "-" + groupName); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { GroupHeaderCreator subVisitor = new GroupHeaderCreator(groupName, bandInfo, subcolumn, xOffset, 0, 0); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, false); if (header.getHeight() == 0) { header = null; } return header; } protected class GroupFooterCreator extends ReverseReportBandCreator { private final String groupName; public GroupFooterCreator(String groupName, ReportBandInfo bandInfo, FillColumn fillColumn, int xOffset, int yOffset, int level) { super(bandInfo, fillColumn, xOffset, yOffset, level); this.groupName = groupName; } @Override protected Cell columnCell(Column column) { return column.getGroupFooter(groupName); } @Override protected Cell columnGroupCell(ColumnGroup group) { return group.getGroupFooter(groupName); } @Override protected ReportBandCreator createSubVisitor(FillColumn subcolumn, int xOffset, int yOffset, int sublevel) { return new GroupFooterCreator(groupName, bandInfo, subcolumn, xOffset, yOffset, sublevel); } } protected JRBand createGroupFooter(String groupName, List fillColumns) { JRDesignBand footer = new JRDesignBand(); footer.setSplitType(SplitTypeEnum.PREVENT); footer.setPrintWhenExpression(table.getGroupFooter(groupName) == null ? null : table.getGroupFooter(groupName).getPrintWhenExpression()); ReportBandInfo bandInfo = new ReportBandInfo(footer, BandTypeEnum.GROUP_FOOTER + "-" + groupName); int xOffset = 0; for (FillColumn subcolumn : fillColumns) { GroupFooterCreator subVisitor = new GroupFooterCreator(groupName, bandInfo, subcolumn, xOffset, 0, 0); subVisitor.visit(); xOffset = subVisitor.xOffset; } setPdfTags(bandInfo, false); if (footer.getHeight() == 0)//FIXMENOW why zero height cells are not generating any content? could be expanding frames... { footer = null; } return footer; } private void setGroupBands(List fillColumns) { TableReportGroup[] groups = mainDataset.getTableGroups(); if (groups != null) { for (TableReportGroup group : groups) { JRBand header = createGroupHeader(group.getName(), fillColumns); if (header != null) { group.setGroupHeader(header); } JRBand footer = createGroupFooter(group.getName(), fillColumns); if (footer != null) { group.setGroupFooter(footer); } } } } private void setPdfTags(ReportBandInfo bandInfo, boolean isHeader) { if (!isGeneratePdfTags) { return; } String cellTagProp = isHeader ? JRPdfExporterTagHelper.PROPERTY_TAG_TH : JRPdfExporterTagHelper.PROPERTY_TAG_TD; List rows = bandInfo.getRows(); for (BandRowInfo row : rows) { List cells = row.getCells(); CellInfo firstCell = cells.get(0); firstCell.getElement().getPropertiesMap().setProperty(JRPdfExporterTagHelper.PROPERTY_TAG_TR, JRPdfExporterTagHelper.TAG_START); for (CellInfo cell : cells) { cell.getElement().getPropertiesMap().setProperty(cellTagProp, JRPdfExporterTagHelper.TAG_FULL); if (cell.getRowSpan() > 1) { cell.getElement().getPropertiesMap().setProperty(JRPdfExporterTagHelper.PROPERTY_TAG_ROWSPAN, String.valueOf(cell.getRowSpan())); } if (cell.getColSpan() > 1) { cell.getElement().getPropertiesMap().setProperty(JRPdfExporterTagHelper.PROPERTY_TAG_COLSPAN, String.valueOf(cell.getColSpan())); } } CellInfo lastCell = cells.get(cells.size() - 1); lastCell.getElement().getPropertiesMap().setProperty( JRPdfExporterTagHelper.PROPERTY_TAG_TR, cells.size() == 1 ? JRPdfExporterTagHelper.TAG_FULL : JRPdfExporterTagHelper.TAG_END ); } } protected boolean isGeneratePdfTags() { return isGeneratePdfTags; } protected static final String TABLE_SCRIPTLET_NAME = "__Table"; protected class SummaryGroupFooterPrintWhenEvaluator implements BuiltinExpressionEvaluator { private JRValueParameter tableScriptletParam; private TableReportScriptlet tableScriptlet; @Override public void init(Map parametersMap, Map fieldsMap, Map variablesMap, WhenResourceMissingTypeEnum resourceMissingType) throws JRException { tableScriptletParam = parametersMap.get(TABLE_SCRIPTLET_NAME + JRScriptlet.SCRIPTLET_PARAMETER_NAME_SUFFIX); } protected void ensureValue() { if (tableScriptlet == null) { tableScriptlet = (TableReportScriptlet) tableScriptletParam.getValue(); } } @Override public Object evaluate(DatasetExpressionEvaluator evaluator) throws JRExpressionEvalException { ensureValue(); return tableScriptlet.hasDetailOnPage(); } @Override public Object evaluateEstimated(DatasetExpressionEvaluator evaluator) throws JRExpressionEvalException { ensureValue(); return tableScriptlet.hasDetailOnPage(); } @Override public Object evaluateOld(DatasetExpressionEvaluator evaluator) throws JRExpressionEvalException { ensureValue(); return tableScriptlet.hasDetailOnPage(); } } protected int computeTableWidth(List fillColumns) { int width = 0; for (FillColumn column : fillColumns) { width += column.getWidth(); } return width; } protected void addSummaryGroup(List fillColumns) { JRDesignGroup summaryGroup = new JRDesignGroup(); summaryGroup.setName(SUMMARY_GROUP_NAME);//TODO check for uniqueness JRDesignBand groupFooter = new JRDesignBand(); groupFooter.setSplitType(SplitTypeEnum.PREVENT); groupFooter.setHeight(pageFooter.getHeight()); // we need to put everything in a frame so that we can tell the frame // not to print when there are no detail bands on the current page // // we can't do that directly to the band since its print when expression // is evaluated too soon JRDesignFrame footerFrame = new JRDesignFrame(); UUID uuid = DigestUtils.instance().deriveUUID( fillContext.getComponentElement().getUUID(), BandTypeEnum.GROUP_FOOTER + "-" + SUMMARY_GROUP_NAME); footerFrame.setUUID(uuid); footerFrame.setX(0); footerFrame.setY(0); footerFrame.setWidth(computeTableWidth(fillColumns)); footerFrame.setHeight(pageFooter.getHeight()); footerFrame.getLineBox().setPadding((Integer)0); footerFrame.getLineBox().getPen().setLineWidth((Float)0f); footerFrame.setRemoveLineWhenBlank(true); JRDesignExpression footerPrintWhen = builtinEvaluatorFactory.createExpression(new SummaryGroupFooterPrintWhenEvaluator()); footerFrame.setPrintWhenExpression(footerPrintWhen); // clone the contents of the page footer in the frame List footerElements = pageFooter.getChildren(); for (Iterator iterator = footerElements.iterator(); iterator .hasNext();) { JRChild child = iterator.next(); JRChild childClone = (JRChild) child.clone(footerFrame); if (childClone instanceof JRElement) { footerFrame.addElement((JRElement) childClone); } else if (childClone instanceof JRElementGroup) { footerFrame.addElementGroup((JRElementGroup) childClone); } else { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_UNKNOWN_CHILD_TYPE, new Object[]{childClone.getClass().getName()} ); } } groupFooter.addElement(footerFrame); ((JRDesignSection) summaryGroup.getGroupFooterSection()).addBand(groupFooter); mainDataset.addScriptlet(TABLE_SCRIPTLET_NAME, TableReportScriptlet.class); mainDataset.addFirstGroup(summaryGroup); } protected JRElement createCell(JRElementGroup parentGroup, BaseCell cell, int originalWidth, int width, int x, int y, Integer columnHashCode, UUID uuid, boolean forceFrame) { if (!forceFrame) { JRElement cellElement = createCellElement(parentGroup, cell, originalWidth, width, x, y, columnHashCode); if (cellElement != null) { return cellElement; } } JRDesignFrame frame = new JRDesignFrame(this); frame.setUUID(uuid); frame.setElementGroup(parentGroup); frame.setX(x); frame.setY(y); frame.setWidth(width); frame.setHeight(cell.getHeight()); frame.setPositionType(PositionTypeEnum.FLOAT); frame.setStretchType(StretchTypeEnum.ELEMENT_GROUP_HEIGHT); frame.setStyle(cell.getStyle()); frame.setStyleNameReference(cell.getStyleNameReference()); frame.copyBox(cell.getLineBox()); if (columnHashCode != null && headerHtmlBaseProperties.get(columnHashCode) != null) { JRPropertiesMap propertiesMap = frame.getPropertiesMap(); assert propertiesMap != null && propertiesMap.getBaseProperties() == null; propertiesMap.setBaseProperties(headerHtmlBaseProperties.get(columnHashCode)); } // not transferring cell properties to the frame/element for now for (Iterator it = cell.getChildren().iterator(); it.hasNext();) { JRChild child = it.next(); if (child instanceof JRElement) { JRElement element = (JRElement) child; // clone the element in order to set the frame as group element = (JRElement) element.clone(frame); if (width != originalWidth) { scaleCellElement(element, originalWidth, width); if (element instanceof JRElementGroup)//i.e. frame { JRElementGroup elementGroup = (JRElementGroup) element; for (JRElement subelement : elementGroup.getElements()) { scaleCellElement(subelement, originalWidth, width); } } } frame.addElement(element); } else if (child instanceof JRElementGroup) { JRElementGroup elementGroup = (JRElementGroup) child; // clone the elements in order to set the frame as group elementGroup = (JRElementGroup) elementGroup.clone(frame); frame.addElementGroup(elementGroup); if (width != originalWidth) { for (JRElement element : elementGroup.getElements()) { scaleCellElement(element, originalWidth, width); } } } else { throw new JRRuntimeException( EXCEPTION_MESSAGE_KEY_UNKNOWN_CHILD_TYPE, new Object[]{child.getClass().getName()} ); } } return frame; } protected JRElement createCellElement(JRElementGroup elementGroup, BaseCell cell, int originalWidth, int width, int x, int y, Integer columnHashCode) { List children = cell.getChildren(); if (children.size() != 1) { // several children return null; } JRChild child = children.get(0); if (!(child instanceof JRStaticText || child instanceof JRTextField)) { // only doing this for texts for now return null; } JRElement element = (JRElement) child; if (element.getX() != 0 || element.getY() != 0 || element.getWidth() != originalWidth || element.getHeight() != cell.getHeight()) { // the element is not as large as the cell return null; } ModeEnum elementMode = StyleUtil.instance().resolveElementMode(element); if (elementMode == null || elementMode == ModeEnum.TRANSPARENT) { // if the element is not necessarily opaque, check that the cell is transparent ModeEnum cellMode = StyleUtil.instance().resolveMode(cell); if (cellMode != ModeEnum.TRANSPARENT) { // the cell is not necessarily transparent, we need the frame return null; } } if (StyleUtil.instance().hasBox(cell)) { // the cell has box, we need the frame return null; } JRElement cellElement = element.clone(elementGroup, y); cellElement.setX(x); cellElement.setWidth(width); cellElement.setStretchType(StretchTypeEnum.ELEMENT_GROUP_HEIGHT); if (columnHashCode != null && headerHtmlBaseProperties.get(columnHashCode) != null) { JRPropertiesMap propertiesMap = cellElement.getPropertiesMap(); assert propertiesMap != null && propertiesMap.getBaseProperties() == null; propertiesMap.setBaseProperties(headerHtmlBaseProperties.get(columnHashCode)); } if (width != originalWidth) { scaleCellElement(element, originalWidth, width); } return cellElement; } protected void scaleCellElement(JRElement element, Integer cellWidth, int scaledCellWidth) { int scaledX = (int) Math.floor(((float) element.getX() * scaledCellWidth) / cellWidth); element.setX(scaledX); int scaledWidth = (int) Math.floor(((float) element.getWidth() * scaledCellWidth) / cellWidth); element.setWidth(scaledWidth); } protected JRSection wrapBand(JRBand band, JROrigin origin) { JRDesignSection section = new JRDesignSection(origin); section.addBand(band); return section; } @Override public JRBand getBackground() { return null; } @Override public int getBottomMargin() { return 0; } @Override public int getColumnCount() { return 1; } @Override public JRBand getColumnFooter() { return null; } @Override public JRBand getColumnHeader() { return columnHeader; } @Override public int getColumnSpacing() { return 0; } @Override public int getColumnWidth() { return fillContext.getComponentElement().getWidth(); } @Override public JRDataset[] getDatasets() { return parentReport.getDatasets(); } @Deprecated public JRBand getDetail() { // see #getDetailSection() return null; } @Override public JRSection getDetailSection() { return detail; } @Override public JRField[] getFields() { return mainDataset.getFields(); } protected JRField getField(String name) { JRField found = null; for (JRField field : getFields()) { if (name.equals(field.getName())) { found = field; break; } } return found; } @Override public String getFormatFactoryClass() { return parentReport.getFormatFactoryClass(); } @Override public JRGroup[] getGroups() { return mainDataset.getGroups(); } @Override public String[] getImports() { return parentReport.getImports(); } @Override public String getLanguage() { return parentReport.getLanguage(); } @Override public JRBand getLastPageFooter() { return lastPageFooter; } @Override public int getLeftMargin() { return 0; } @Override public JRDataset getMainDataset() { return mainDataset; } @Override public String getName() { return mainDataset.getName(); } @Override public JRBand getNoData() { return noData; } @Override public OrientationEnum getOrientationValue() { return OrientationEnum.PORTRAIT; } @Override public JRBand getPageFooter() { return pageFooter; } @Override public JRBand getPageHeader() { return null; } @Override public int getPageHeight() { return parentReport.getPageHeight(); } @Override public int getPageWidth() { return fillContext.getComponentElement().getWidth(); } @Override public JRParameter[] getParameters() { return mainDataset.getParameters(); } @Override public PrintOrderEnum getPrintOrderValue() { return PrintOrderEnum.VERTICAL; } @Override public RunDirectionEnum getColumnDirection() { return RunDirectionEnum.LTR; } @Override public String getProperty(String name) { return mainDataset.getPropertiesMap().getProperty(name); } @Override public String[] getPropertyNames() { return mainDataset.getPropertiesMap().getPropertyNames(); } @Override public JRQuery getQuery() { return mainDataset.getQuery(); } @Override public String getResourceBundle() { return mainDataset.getResourceBundle(); } @Override public int getRightMargin() { return 0; } @Override public String getScriptletClass() { return mainDataset.getScriptletClass(); } @Override public JRScriptlet[] getScriptlets() { return mainDataset.getScriptlets(); } @Override public JRSortField[] getSortFields() { return mainDataset.getSortFields(); } @Override public JRStyle[] getStyles() { return parentReport.getStyles(); } @Override public JRBand getSummary() { return summary; } @Override public JRReportTemplate[] getTemplates() { // the parent report's templates are always used for the subreport return null; } @Override public JRBand getTitle() { return title; } @Override public int getTopMargin() { return 0; } @Override public JRVariable[] getVariables() { return mainDataset.getVariables(); } protected JRVariable getVariable(String name) { JRVariable found = null; for (JRVariable var : getVariables()) { if (name.equals(var.getName())) { found = var; break; } } return found; } @Override public WhenNoDataTypeEnum getWhenNoDataTypeValue() { WhenNoDataTypeTableEnum whenNoDataType = table.getWhenNoDataType(); if (whenNoDataType == null) { whenNoDataType = WhenNoDataTypeTableEnum.getByName( propertiesUtil.getProperty( fillContext.getFillDataset(), TableComponent.CONFIG_PROPERTY_WHEN_NO_DATA_TYPE ) ); } switch (whenNoDataType) { case ALL_SECTIONS_NO_DETAIL : { return WhenNoDataTypeEnum.ALL_SECTIONS_NO_DETAIL; } case NO_DATA_CELL : { return WhenNoDataTypeEnum.NO_DATA_SECTION; } case BLANK : default : { return WhenNoDataTypeEnum.NO_PAGES; } } } @Override public SectionTypeEnum getSectionType() { return SectionTypeEnum.BAND; } @Override public WhenResourceMissingTypeEnum getWhenResourceMissingTypeValue() { return mainDataset.getWhenResourceMissingTypeValue(); } @Override public boolean isFloatColumnFooter() { return true; } @Override public boolean isIgnorePagination() { return false; } @Override public boolean isSummaryNewPage() { return false; } @Override public boolean isSummaryWithPageHeaderAndFooter() { return false; } @Override public boolean isTitleNewPage() { return false; } @Override public void removeProperty(String name) { throw new UnsupportedOperationException(); } @Override public void setProperty(String name, String value) { throw new UnsupportedOperationException(); } @Override public void setWhenNoDataType(WhenNoDataTypeEnum whenNoDataType) { throw new UnsupportedOperationException(); } @Override public void setWhenResourceMissingType( WhenResourceMissingTypeEnum whenResourceMissingType) { throw new UnsupportedOperationException(); } @Override public JRStyle getDefaultStyle() { return parentReport.getDefaultStyle(); } @Override public StyleResolver getStyleResolver() { return parentReport.getStyleResolver(); } @Override public JRPropertiesHolder getParentProperties() { return null; } @Override public JRPropertiesMap getPropertiesMap() { return mainDataset.getPropertiesMap(); } @Override public boolean hasProperties() { return mainDataset.hasProperties(); } @Override public DatasetPropertyExpression[] getPropertyExpressions() { return mainDataset.getPropertyExpressions(); } @Override public UUID getUUID() { return mainDataset.getUUID(); } public boolean isInteractiveTable() { return isInteractiveTable; } public void setTableInstanceIndex(int instanceIndex) { for (TableIndexProperties properties : tableIndexProperties) { properties.setTableInstanceIndex(instanceIndex); } } // creates a JRPropertiesMap instance that is used as base properties for table elements. // on each table instantiation, a property in the base instance changes its value and the // value propagates to the print elements created by the table. protected static class TableIndexProperties { private final String propertyName; private final String classFixedPart; private JRPropertiesMap propertiesMap; public TableIndexProperties(String propertyName, String classFixedPart) { this.propertyName = propertyName; this.classFixedPart = classFixedPart; this.propertiesMap = new JRPropertiesMap(); } public JRPropertiesMap getPropertiesMap() { return propertiesMap; } public void setTableInstanceIndex(int instanceIndex) { propertiesMap.setProperty(propertyName, classFixedPart + instanceIndex); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy