JasperReportWrapper.java
/*
** Module : JasperReportWrapper.java
** Abstract : Jasper reporting facility implemented as 4GL syntax extension.
**
** Copyright (c) 2017-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description----------------------------------
** 001 SVL 20171030 Created initial version.
** 002 SVL 20180205 Switched to data sources concept.
** 003 HC 20180410 Renamed ReportOutputFormat to MediaType.
** 004 EVL 20180423 Renamed Report to FwdReport. Adding support for decimal to Integer mapping.
** Also added case when we will bypass data mapping. Fixes jasperreport file
** handling exceptions that causing application to end.
** 005 OM 20181003 Text in cells is cut only when constrained by page layout (PDF).
** 006 CA 20191005 Added export-report-html for FWD report.
** 006 EVL 20210212 Adding methods to handle chart related reports.
** EVL 20210305 Adding methods to handle rtf/docx related reports. Fixed output stream issue for RTF
** exporter.
** 007 RNC 20250131 When there are too many columns in a PDF report, split the report into multiple pages
* rather than shrinking the cells to fit.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program 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 Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.reporting;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.ui.BrowseWidget;
import com.goldencode.p2j.util.*;
import net.sf.jasperreports.engine.*;
import net.sf.jasperreports.engine.base.JRBasePrintPage;
import net.sf.jasperreports.engine.export.*;
import net.sf.jasperreports.engine.export.ooxml.JRDocxExporter;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.engine.fill.JRTemplatePrintText;
import net.sf.jasperreports.export.*;
import java.io.*;
import java.math.*;
import java.util.*;
import static com.goldencode.p2j.reporting.ReportFactory.showError;
/**
* Jasper reporting facility implemented as 4GL syntax extension.
*/
class JasperReportWrapper
implements FwdReport
{
/** Buffer size for exporting report to a file. */
private final static int OUTPUT_BUFFER_SIZE = 64000;
/** Report parameters (the ones which were set in 4GL code). */
private Map<String, BaseDataType> parameters;
/** Data source used by this report. */
private JasperDataSource dataSource;
/** File name of the report design file. */
private String designFileName;
/** The resource ID. */
private Long id;
/**
* Get Jasper-compatible value from the 4GL-compatible value. In most cases Jasper-compatible
* value has Java primitive data type.
*
* @param value
* 4GL-compatible value
* @param jasperValueClass
* Expected Jasper-compatible data type.
*
* @return Jasper-compatible value.
*
* @throws IllegalArgumentException
* If 4GL-compatible data type is not supported or doesn't match expected
* Jasper-compatible data type.
*/
static Object getJavaValue(BaseDataType value, Class<?> jasperValueClass)
{
// first handle case when no need to make data transformation
if (value.getClass().equals(jasperValueClass))
{
return value;
}
else if (value instanceof Text && String.class.equals(jasperValueClass))
{
return ((character) value).getValue();
}
else if (value instanceof logical && Boolean.class.equals(jasperValueClass))
{
return !value.isUnknown() ? ((logical) value).getValue() : null;
}
else if (value instanceof int64)
{
if (Integer.class.equals(jasperValueClass))
{
return ((int64) value).toJavaIntegerType();
}
else if (Long.class.equals(jasperValueClass))
{
return ((int64) value).toJavaLongType();
}
else if (Short.class.equals(jasperValueClass))
{
Integer val = ((int64) value).toJavaIntegerType();
return val != null ? val.shortValue() : null;
}
}
else if (value instanceof decimal)
{
if (Double.class.equals(jasperValueClass))
{
BigDecimal val = ((decimal) value).toBigDecimal();
return val != null ? val.doubleValue() : null;
}
else if (Float.class.equals(jasperValueClass))
{
BigDecimal val = ((decimal) value).toBigDecimal();
return val != null ? val.floatValue() : null;
}
else if (Integer.class.equals(jasperValueClass))
{
BigDecimal val = ((decimal) value).toBigDecimal();
return val != null ? val.intValueExact() : null;
}
else if (BigDecimal.class.equals(jasperValueClass))
{
return ((decimal) value).toBigDecimal();
}
}
else if (value instanceof date && Date.class.equals(jasperValueClass))
{
return !value.isUnknown() ? ((date) value).dateValue() : null;
}
throw new IllegalArgumentException(
String.format("Value of type %s cannot be converted to Jasper report parameter " +
"of type %s", value.getClass(), jasperValueClass));
}
/**
* Set the parameter used by the report.
*
* @param param
* Name of the parameter.
* @param value
* Value of the parameter.
*
* @return <code>true</code> on success.
*/
@Override
public logical setReportParameter(String param, BaseDataType value)
{
return setReportParameter(new character(param), value);
}
/**
* Set the parameter used by the report.
*
* @param param
* Name of the parameter.
* @param value
* Value of the parameter.
*
* @return <code>true</code> on success.
*/
@Override
public logical setReportParameter(String param, String value)
{
return setReportParameter(new character(param), new character(value));
}
/**
* Set the parameter used by the report.
*
* @param param
* Name of the parameter.
* @param value
* Value of the parameter.
*
* @return <code>true</code> on success.
*/
@Override
public logical setReportParameter(character param, BaseDataType value)
{
if (param.isUnknown())
{
showError("Report parameter name cannot be an unknown value.");
return new logical(false);
}
if (parameters == null)
{
parameters = new HashMap<>();
}
parameters.put(param.getValue(), value);
return new logical(true);
}
/**
* Run report end export result to the PDF file.
*
* @param fileName
* Name of the output PDF file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportPdf(String fileName)
{
return exportReportPdf(new character(fileName));
}
/**
* Run report end export result to the PDF file.
*
* @param fileName
* Name of the output PDF file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportPdf(character fileName)
{
return exportReport(fileName, MediaType.PDF);
}
/**
* Run report end export result to the XLS file.
*
* @param fileName
* Name of the output XLS file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportXls(String fileName)
{
return exportReportXls(new character(fileName));
}
/**
* Run report end export result to the XLS file.
*
* @param fileName
* Name of the output XLS file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportXls(character fileName)
{
return exportReport(fileName, MediaType.XLS);
}
/**
* Run report end export result to the XLSX file.
*
* @param fileName
* Name of the output XLSX file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportXlsx(String fileName)
{
return exportReportXlsx(new character(fileName));
}
/**
* Run report end export result to the XLSX file.
*
* @param fileName
* Name of the output XLSX file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportXlsx(character fileName)
{
return exportReport(fileName, MediaType.XLSX);
}
/**
* Run report end export result to the RTF file.
*
* @param fileName
* Name of the output RTF file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportRtf(String fileName)
{
return exportReportRtf(new character(fileName));
}
/**
* Run report end export result to the RTF file.
*
* @param fileName
* Name of the output RTF file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportRtf(character fileName)
{
return exportReport(fileName, MediaType.RTF);
}
/**
* Run report end export result to the DOCX file.
*
* @param fileName
* Name of the output DOCX file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportDocx(String fileName)
{
return exportReportDocx(new character(fileName));
}
/**
* Run report end export result to the DOCX file.
*
* @param fileName
* Name of the output DOCX file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportDocx(character fileName)
{
return exportReport(fileName, MediaType.DOCX);
}
/**
* Run report end export result to the CSV file.
*
* @param fileName
* Name of the output CSV file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportCsv(String fileName)
{
return exportReportCsv(new character(fileName));
}
/**
* Run report end export result to the CSV file.
*
* @param fileName
* Name of the output CSV file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportCsv(character fileName)
{
return exportReport(fileName, MediaType.CSV);
}
/**
* Import CSV file as data source for report generation.
*
* @param fileName
* Name of the output CSV file.
*
* @return <code>true</code> on success.
*/
@Override
public logical importCsvDataSource(character fileName)
{
return importCsvDataSource(fileName.toStringMessage());
}
/**
* Import CSV file as data source for report generation.
*
* @param fileName
* Name of the output CSV file.
*
* @return <code>true</code> on success.
*/
@Override
public logical importCsvDataSource(String fileName)
{
return new logical(true);
}
/**
* Gets the total coluns range of the report sheet.
*
* @param sheetName
* The name of the sheet to inspect.
*
* @return The total columns count.
*/
@Override
public integer getSheetRangeColumns(character sheetName)
{
return getSheetRangeColumns(sheetName.toStringMessage());
}
/**
* Gets the total coluns range of the report sheet.
*
* @param sheetName
* The name of the sheet to inspect.
*
* @return The total columns count.
*/
@Override
public integer getSheetRangeColumns(String sheetName)
{
int iRet = 0;
return new integer(iRet);
}
/**
* Gets the total rows range of the report sheet.
*
* @param sheetName
* The name of the sheet to inspect.
*
* @return The total rows count.
*/
@Override
public integer getSheetRangeRows(character sheetName)
{
return getSheetRangeRows(sheetName.toStringMessage());
}
/**
* Gets the total rows range of the report sheet.
*
* @param sheetName
* The name of the sheet to inspect.
*
* @return The total rows count.
*/
@Override
public integer getSheetRangeRows(String sheetName)
{
int iRet = 0;
return new integer(iRet);
}
/**
* Starts to prepare data for new chart.
*/
@Override
public void addChart()
{
}
/**
* Gets the total rows range of the report sheet.
*
* @param pictName
* The name of the picture file.
* @param x
* X position of the picture.
* @param y
* Y position of the picture.
* @param w
* Width of the picture.
* @param h
* Height of the picture.
*/
@Override
public void chartAddPicture(character pictName, int x, int y, int w, int h)
{
chartAddPicture(pictName.toStringMessage(), x, y, w, h);
}
/**
* Gets the total rows range of the report sheet.
*
* @param pictName
* The name of the picture file.
* @param x
* X position of the picture.
* @param y
* Y position of the picture.
* @param w
* Width of the picture.
* @param h
* Height of the picture.
*/
@Override
public void chartAddPicture(String pictName, int x, int y, int w, int h)
{
// TODO: Implement this
}
/**
* Run report end export result to the HTML file.
*
* @param fileName
* Name of the output HTML file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportHtml(String fileName)
{
return exportReportHtml(new character(fileName));
}
/**
* Run report end export result to the HTML file.
*
* @param fileName
* Name of the output HTML file.
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReportHtml(character fileName)
{
return exportReport(fileName, MediaType.HTML);
}
/**
* Set report design file.
*
* @param designFileName
* Name of the report design file.
*/
@Override
public void setReportDesign(character designFileName)
{
if (designFileName.isUnknown() || designFileName.getValue().isEmpty())
{
showError("Report design file name cannot be an unknown value or empty string.");
return;
}
this.designFileName = designFileName.getValue();
}
/**
* Set report design file.
*
* @param designFileName
* Name of the report design file.
*/
@Override
public void setReportDesign(String designFileName)
{
setReportDesign(new character(designFileName));
}
/**
* Get the name of the report design file.
*
* @return the name of the report design file.
*/
@Override
public character getReportDesign()
{
return new character(designFileName);
}
/**
* Set the data source used by this report.
*
* @param dataSource
* Handle to the data source.
*/
@Override
public void setReportDataSource(handle dataSource)
{
Object resource = dataSource.get();
if (resource instanceof JasperDataSource)
{
this.dataSource = (JasperDataSource) resource;
}
else if (resource instanceof QueryWrapper)
{
this.dataSource = new QueryJasperDataSource((QueryWrapper) resource);
}
else if (resource instanceof BrowseWidget)
{
BrowseWidget browse = (BrowseWidget) resource;
// designFileName will be set later when we know the output format
this.dataSource = new BrowseJasperDataSource(browse);
}
else
{
showError(String.format(
"Invalid handle of type %s was was passed an report's DATA-SOURCE attribute",
((HandleResource) resource).type()));
}
}
/**
* Set the query used as the data source by this report.
*
* @param dataSource
* Handle to the query used as the data source by this report.
*/
@Override
public void setReportDataSource(QueryWrapper dataSource)
{
setReportDataSource(new handle(dataSource));
}
/**
* Get the data source used by this report.
*
* @return handle to the data source used by this report.
*/
@Override
public handle getReportDataSource()
{
return dataSource.asProgressObject();
}
/**
* Reports if this object is valid for use.
*
* @return always <code>true</code>.
*/
@Override
public boolean valid()
{
return true;
}
/**
* Reports if this object is unknown.
*
* @return always <code>false</code>.
*/
@Override
public boolean unknown()
{
return false;
}
/**
* Get this resource's ID, if is already set.
* <p>
* This is for internal usage only. If you need the resource ID, use
* {@link handle#resourceId}, which will compute it and save it at the resource, too.
*
* @return The resource's ID or <code>null</code> if not set.
*/
@Override
public Long id()
{
return id;
}
/**
* Set this resource's ID.
*
* @param id
* The resource's ID.
*/
@Override
public void id(long id)
{
this.id = id;
}
/**
* Run report end export result using the specified format to the specified file.
*
* @param outputFileName
* Name of the output file.
* @param outputFormat
* Output format (e.g. PDF).
*
* @return <code>true</code> on success.
*/
@Override
public logical exportReport(character outputFileName, MediaType outputFormat)
{
if (designFileName == null)
{
showError("Cannot execute report: REPORT-DESIGN is not set.");
return new logical(false);
}
if (dataSource == null)
{
showError("Cannot execute report: REPORT-DATA-SOURCE is not set.");
return new logical(false);
}
if (outputFileName.isUnknown())
{
showError(String.format("Output file name is not specified for report %s.",
designFileName));
return new logical(false);
}
// load report file
JasperReport report = dataSource.getReport(designFileName);
if (report == null)
{
return new logical(false);
}
// convert Report parameters from 4GL to Jasper data types
Map<String, Object> jasperParameters = new HashMap<>();
if (parameters != null)
{
for (JRParameter parameter : report.getParameters())
{
String parameterName = parameter.getName();
BaseDataType progressValue = parameters.get(parameterName);
if (progressValue != null)
{
try
{
jasperParameters.put(parameterName, getJavaValue(progressValue,
parameter.getValueClass()));
}
catch (IllegalArgumentException e)
{
showError("Invalid data type of report parameter " + parameterName, e);
return new logical(false);
}
}
}
}
if (!dataSource.reportStarted(report))
{
return new logical(false);
}
// fill report with data
boolean error = false;
try
{
JasperPrint jasperPrint;
try
{
jasperPrint = JasperFillManager.fillReport(report, jasperParameters, dataSource);
}
catch (JRException e)
{
error = true;
showError(String.format("Cannot fill report %s", designFileName), e);
return new logical(false);
}
catch (JasperReportException jre)
{
error = true;
showError(String.format("Internal error for the report %s", designFileName), jre);
return new logical(false);
}
// if columns overflowed the page width, we need to split each page
String colNoProperty = report.getProperty("colNo");
if (colNoProperty != null)
{
int colNo = Integer.parseInt(colNoProperty);
int splitsPerPage = Integer.parseInt(report.getProperty("splitsPerPage"));
List<JRPrintPage> oldPages = jasperPrint.getPages();
List<JRPrintPage> newPages = splitPages(oldPages, colNo, splitsPerPage);
oldPages.clear();
oldPages.addAll(newPages);
}
// export report
String output = outputFileName.getValue();
Stream reportStream;
try
{
reportStream = StreamFactory.openFileStream(output, true, false);
}
catch (ErrorConditionException e)
{
error = true;
showError(String.format("Cannot export report stream for file %s", output), e);
return new logical(false);
}
OutputStream streamWrapper =
new BufferedOutputStream(new OutputStreamWrapper(reportStream), OUTPUT_BUFFER_SIZE);
try
{
Exporter exporter;
switch (outputFormat)
{
case CSV:
exporter = new JRCsvExporter();
exporter.setExporterOutput(new SimpleWriterExporterOutput(streamWrapper));
break;
case DOCX:
exporter = new JRDocxExporter();
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(streamWrapper));
break;
case HTML:
exporter = new HtmlExporter();
exporter.setExporterOutput(new SimpleHtmlExporterOutput(streamWrapper));
break;
case PDF:
exporter = new JRPdfExporter();
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(streamWrapper));
break;
case RTF:
exporter = new JRRtfExporter();
exporter.setExporterOutput(new SimpleWriterExporterOutput(streamWrapper));
break;
case XLS:
exporter = new JRXlsExporter();
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(streamWrapper));
break;
case XLSX:
exporter = new JRXlsxExporter();
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(streamWrapper));
break;
default:
throw new IllegalArgumentException("Invalid export format " + outputFormat);
}
exporter.setExporterInput(new SimpleExporterInput(jasperPrint));
exporter.exportReport();
}
catch (JRException e)
{
error = true;
showError(String.format("Cannot export report %s into %s",
designFileName, output), e);
return new logical(false);
}
finally
{
try
{
streamWrapper.flush();
streamWrapper.close();
}
catch (IOException e)
{
if (!error)
{
error = true;
showError(String.format("Cannot flush report %s into %s",
designFileName, output), e);
return new logical(false);
}
}
}
}
finally
{
dataSource.reportFinished(error);
}
return new logical(true);
}
/**
* Splits every given page into multiple ones. A fresh page will be created by moving the column
* and data cells until it detects that the X coordinate goes back to the start of the page.
* The X coordinate need to be already arranged before the splitting is performed because the method
* will just copy them as they are to new pages.<p>
* Used primarily when there are too many columns in a PDF report, being an alternative to shrinking
* down the columns in order for all to fit, which can make the cells unintelligible.
*
* @param pages
* The pages which will be split.
* @param colNo
* The number of columns for the data source. Used to iterate through the data cells.
* @param splitsPerPage
* Represents how many times we need to create a fresh page due to a split.
*
* @return A list with all the fresh pages created.
*/
private List<JRPrintPage> splitPages(List<JRPrintPage> pages, int colNo, int splitsPerPage)
{
List<JRPrintPage> newPages = new ArrayList<>();
if (pages == null || pages.isEmpty())
{
return newPages;
}
if (colNo == 1)
{
return pages;
}
int startRow = 1;
int columnStart;
for (int i = 0, len = pages.size(); i < len; i++)
{
JRPrintPage page = pages.get(i);
List<JRPrintPage> freshPages = splitPage(page, i, colNo, len, splitsPerPage, startRow);
if (freshPages != null)
{
newPages.addAll(freshPages);
}
columnStart = i == 0 ? 1 : 0;
startRow += (page.getElements().size() - columnStart - colNo - 2) / colNo;
}
return newPages;
}
/**
* Actual implementation of splitting a single page into multiple ones.
*
* @param page
* The page which will be split.
* @param pageNo
* The number of the page from the original report. If <code>0</code>,
* it will assume the first element is a report title, and it will include it.
* @param colNo
* The number of columns for the data source. Used to iterate through the data cells.
* @param oldPagesNumber
* The size (pages number) of the report from which the page was taken. Used to customize the
* pagination footer.
* @param splitsPerPage
* Represents how many times we need to create a fresh page due to a split. Used to customize
* the pagination footer.
* @param startRow
* The 1-indexed number of the first row present in the page. Used to customize the pagination
* footer.
*
* @return A list with the fresh pages created, or <code>null</code> if the page is null or empty,
* or the column number is less or equal than 1 which means the page can't be split.
*/
private List<JRPrintPage> splitPage(JRPrintPage page, int pageNo, int colNo,
int oldPagesNumber, int splitsPerPage, int startRow)
{
if (page == null)
{
return null;
}
List<JRPrintElement> oldElements = page.getElements();
if (pageNo < 0 || oldElements == null || oldElements.isEmpty() || colNo <= 1)
{
return null;
}
List<JRPrintPage> newPages = new ArrayList<>();
JRPrintPage newPage = new JRBasePrintPage();
List<JRPrintElement> newElements = newPage.getElements();
int oldElementsSize = oldElements.size();
int columnStart = 0;
if (pageNo == 0)
{
// add the report title
columnStart = 1;
newElements.add(oldElements.get(0));
}
int totalPageNumber = (splitsPerPage + 1) * oldPagesNumber;
int currentPageNumber = (splitsPerPage + 1) * pageNo + 1;
for (int i = columnStart; i < colNo + columnStart; i++)
{
JRPrintElement currentElement = oldElements.get(i);
int currentX = currentElement.getX();
int nextX = oldElements.get(i + 1).getX();
while (currentX < nextX)
{
// add the current column + all the data cells for this column
newElements.add(currentElement);
int index = colNo + i;
// when we reach at the last 2 page elements which are considered to be the page numbers, exit
while (oldElementsSize - index > 2)
{
newElements.add(oldElements.get(index));
index += colNo;
}
if (++i == colNo + columnStart)
{
// we reached at the last column
break;
}
else
{
currentX = nextX;
currentElement = oldElements.get(i);
nextX = oldElements.get(i + 1).getX();
}
}
if (i != colNo + columnStart)
{
// we reached at the split point, add the last column + data cells to the page
newElements.add(currentElement);
int index = colNo + i;
while (oldElementsSize - index > 2)
{
newElements.add(oldElements.get(index));
index += colNo;
}
}
// clone and customize the footer element
int endRow = startRow + (oldElementsSize - columnStart - colNo - 2) / colNo - 1;
String pageInfo = totalPageNumber + ", Rows " + startRow + " to " + endRow;
JRTemplatePrintText oldFooterElement = (JRTemplatePrintText) oldElements.get(oldElementsSize - 2);
JRTemplatePrintText newFooterElement = cloneTextElementCore(oldFooterElement);
String customPage = "Page " + currentPageNumber++ + " of " + pageInfo;
newFooterElement.setText(customPage);
newElements.add(newFooterElement);
newPages.add(newPage);
newPage = new JRBasePrintPage();
newElements = newPage.getElements();
}
return newPages;
}
/**
* Clones only the core fields from a text element. Properties such as hyperlinks are omitted.
*
* @param element
* The element to be cloned.
*
* @return A partial copy with only the core fields set.
*/
private JRTemplatePrintText cloneTextElementCore(JRTemplatePrintText element)
{
JRTemplatePrintText newElement = new JRTemplatePrintText();
newElement.setText(element.getFullText());
newElement.setValue(element.getValue());
newElement.setLineSpacingFactor(element.getLineSpacingFactor());
newElement.setLeadingOffset(element.getLeadingOffset());
newElement.setRunDirection(element.getRunDirectionValue());
newElement.setTextHeight(element.getTextHeight());
newElement.setTemplate(element.getTemplate());
newElement.setX(element.getX());
newElement.setY(element.getY());
newElement.setHeight(element.getHeight());
newElement.setWidth(element.getWidth());
newElement.setSourceElementId(element.getSourceElementId());
newElement.setAnchorName(element.getAnchorName());
return newElement;
}
}