/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import javax.swing.event.EventListenerList;
import org.pentaho.reporting.engine.classic.core.designtime.Change;
import org.pentaho.reporting.engine.classic.core.designtime.StyleChange;
import org.pentaho.reporting.engine.classic.core.event.ReportModelEvent;
import org.pentaho.reporting.engine.classic.core.event.ReportModelListener;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.engine.classic.core.function.ExpressionCollection;
import org.pentaho.reporting.engine.classic.core.function.StructureFunction;
import org.pentaho.reporting.engine.classic.core.style.StyleSheetCollection;
import org.pentaho.reporting.engine.classic.core.util.IntegerCache;
import org.pentaho.reporting.engine.classic.core.util.ReportProperties;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.engine.classic.core.wizard.DataSchemaDefinition;
import org.pentaho.reporting.engine.classic.core.wizard.DefaultDataSchemaDefinition;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;
/**
* The AbstractReportDefinition serves as base-implementation for both the SubReport and the global JFreeReport
* instance. There's no point to subclass this class any further.
* <p/>
* ReportDefinitions define the query string to "default" by default, change this to reflect the accepted queries in
* your data-source.
*
* @author Thomas Morgner
*/
public abstract class AbstractReportDefinition extends Section
implements ReportDefinition
{
/**
* Storage for arbitrary properties that a user can assign to the report.
*/
private ReportProperties properties;
/**
* The resource bundle factory is used when generating localized reports.
*/
private ResourceBundleFactory resourceBundleFactory;
/**
* Storage for the expressions in the report.
*/
private ExpressionCollection expressions;
/**
* An ordered list of report groups (each group defines its own header and footer).
*/
private Group rootGroup;
/**
* The report header band (printed once at the start of the report).
*/
private ReportHeader reportHeader;
/**
* The report footer band (printed once at the end of the report).
*/
private ReportFooter reportFooter;
/**
* The page header band (printed at the start of every page).
*/
private PageHeader pageHeader;
/**
* The page footer band (printed at the end of every page).
*/
private PageFooter pageFooter;
/**
* The watermark band.
*/
private Watermark watermark;
/**
* The stylesheet collection used for this report.
*/
private StyleSheetCollection styleSheetCollection;
private transient EventListenerList eventListeners;
private long nonVisualsChangeTracker;
private long datasourceChangeTracker;
private DataSchemaDefinition dataSchemaDefinition;
protected AbstractReportDefinition(final InstanceID id)
{
super(id);
init();
}
/**
* Creates a new instance. This initializes all properties to their defaults - especially for subreports you have to
* set sensible values before you can use them later.
*/
protected AbstractReportDefinition()
{
init();
}
private void init()
{
this.dataSchemaDefinition = new DefaultDataSchemaDefinition();
this.properties = new ReportProperties();
this.styleSheetCollection = new StyleSheetCollection();
this.rootGroup = new RelationalGroup();
this.reportHeader = new ReportHeader();
this.reportFooter = new ReportFooter();
this.pageHeader = new PageHeader();
this.pageFooter = new PageFooter();
this.watermark = new Watermark();
this.expressions = new ExpressionCollection();
registerAsChild(rootGroup);
registerAsChild(reportHeader);
registerAsChild(reportFooter);
registerAsChild(pageHeader);
registerAsChild(pageFooter);
registerAsChild(watermark);
addReportModelListener(new ResourceBundleChangeHandler());
}
/**
* Returns the resource bundle factory for this report definition. The {@link ResourceBundleFactory} is used in
* internationalized reports to create the resourcebundles holding the localized resources.
*
* @return the assigned resource bundle factory.
*/
public ResourceBundleFactory getResourceBundleFactory()
{
return resourceBundleFactory;
}
/**
* Redefines the resource bundle factory for the report.
*
* @param resourceBundleFactory the new resource bundle factory, never null.
* @throws NullPointerException if the given ResourceBundleFactory is null.
*/
public void setResourceBundleFactory(final ResourceBundleFactory resourceBundleFactory)
{
this.resourceBundleFactory = resourceBundleFactory;
this.notifyNodePropertiesChanged();
}
public int getPreProcessorCount()
{
final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS);
if (maybeArray instanceof ReportPreProcessor[])
{
final ReportPreProcessor[] preprocessors = (ReportPreProcessor[]) maybeArray;
return preprocessors.length;
}
return 0;
}
public ReportPreProcessor[] getPreProcessors()
{
final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS);
if (maybeArray instanceof ReportPreProcessor[])
{
final ReportPreProcessor[] preprocessors = (ReportPreProcessor[]) maybeArray;
return (ReportPreProcessor[]) preprocessors.clone();
}
return new ReportPreProcessor[0];
}
public ReportPreProcessor getPreProcessor(final int index)
{
final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS);
if (maybeArray instanceof ReportPreProcessor[])
{
final ReportPreProcessor[] preprocessors = (ReportPreProcessor[]) maybeArray;
return preprocessors[index];
}
throw new IndexOutOfBoundsException();
}
public void addPreProcessor(final ReportPreProcessor preProcessor)
{
if (preProcessor == null)
{
throw new NullPointerException();
}
final ReportPreProcessor[] preprocessors = getPreProcessors();
final ArrayList newProcessors = new ArrayList(Math.max(10, preprocessors.length));
for (int i = 0; i < preprocessors.length; i++)
{
final ReportPreProcessor preprocessor = preprocessors[i];
newProcessors.add(preprocessor);
}
newProcessors.add(preProcessor);
final ReportPreProcessor[] newArray =
(ReportPreProcessor[]) newProcessors.toArray(new ReportPreProcessor[newProcessors.size()]);
setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, newArray);
}
public void removePreProcessor(final ReportPreProcessor preProcessor)
{
if (preProcessor == null)
{
throw new NullPointerException();
}
final ReportPreProcessor[] preprocessors = getPreProcessors();
final ArrayList newProcessors = new ArrayList(Math.max(10, preprocessors.length));
boolean found = false;
for (int i = 0; i < preprocessors.length; i++)
{
final ReportPreProcessor preprocessor = preprocessors[i];
if (found || preprocessor != preProcessor)
{
newProcessors.add(preprocessor);
found = true;
}
}
if (found)
{
final ReportPreProcessor[] newArray =
(ReportPreProcessor[]) newProcessors.toArray(new ReportPreProcessor[newProcessors.size()]);
setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, newArray);
}
}
public Group getRootGroup()
{
return rootGroup;
}
public void setRootGroup(final Group rootGroup)
{
if (rootGroup == null)
{
throw new NullPointerException();
}
if (rootGroup instanceof CrosstabGroup == false &&
rootGroup instanceof RelationalGroup == false)
{
throw new IllegalArgumentException("Only Crosstabs or relational-groups are permitted at the root");
}
validateLooping(rootGroup);
if (unregisterParent(rootGroup))
{
return;
}
final Element oldElement = this.rootGroup;
this.rootGroup.setParent(null);
this.rootGroup = rootGroup;
this.rootGroup.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(rootGroup);
}
/**
* Adds a property to the report.
* <p/>
* If a property with the given name already exists, the property will be updated with the new value. If the supplied
* value is <code>null</code>, the property will be removed.
* <p/>
* Developers are free to add any properties they want to a report, and then display those properties in the report.
* For example, you might add a 'user.name' property, so that you can display the username in the header of a report.
*
* @param key the key.
* @param value the value.
* @deprecated Properties should not be used. Use the master-report's parameters instead.
*/
public void setProperty(final String key, final Object value)
{
if (key == null)
{
throw new NullPointerException();
}
this.properties.put(key, value);
this.notifyNodePropertiesChanged();
}
/**
* Returns the report properties collection for this report.
* <p/>
* These properties are inherited to all ReportStates generated for this report.
*
* @return the report properties.
* @deprecated Report-Properties should not be used anymore.
*/
public ReportProperties getProperties()
{
return properties;
}
/**
* Returns the value of the property with the specified key.
*
* @param key the key.
* @return the property value.
* @deprecated Do not use this method anymore. Use the master report's parameters instead.
*/
public Object getProperty(final String key)
{
if (key == null)
{
throw new NullPointerException();
}
return this.properties.get(key);
}
/**
* Sets the report header.
*
* @param header the report header (<code>null</code> not permitted).
*/
public void setReportHeader(final ReportHeader header)
{
if (header == null)
{
throw new NullPointerException("AbstractReportDefinition.setReportHeader(...) : null not permitted.");
}
validateLooping(header);
if (unregisterParent(header))
{
return;
}
final Element oldElement = this.reportHeader;
this.reportHeader.setParent(null);
this.reportHeader = header;
this.reportHeader.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(header);
}
/**
* Returns the report header.
*
* @return the report header (never <code>null</code>).
*/
public ReportHeader getReportHeader()
{
return reportHeader;
}
/**
* Sets the report footer.
*
* @param footer the report footer (<code>null</code> not permitted).
*/
public void setReportFooter(final ReportFooter footer)
{
if (footer == null)
{
throw new NullPointerException("AbstractReportDefinition.setReportFooter(...) : null not permitted.");
}
validateLooping(footer);
if (unregisterParent(footer))
{
return;
}
final Element oldElement = this.reportFooter;
this.reportFooter.setParent(null);
this.reportFooter = footer;
this.reportFooter.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(footer);
}
/**
* Returns the page footer.
*
* @return the report footer (never <code>null</code>).
*/
public ReportFooter getReportFooter()
{
return reportFooter;
}
/**
* Sets the page header.
*
* @param header the page header (<code>null</code> not permitted).
*/
public void setPageHeader(final PageHeader header)
{
if (header == null)
{
throw new NullPointerException("AbstractReportDefinition.setPageHeader(...) : null not permitted.");
}
validateLooping(header);
if (unregisterParent(header))
{
return;
}
final Element oldElement = this.pageHeader;
this.pageHeader.setParent(null);
this.pageHeader = header;
this.pageHeader.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(header);
}
/**
* Returns the page header.
*
* @return the page header (never <code>null</code>).
*/
public PageHeader getPageHeader()
{
return pageHeader;
}
/**
* Sets the page footer.
*
* @param footer the page footer (<code>null</code> not permitted).
*/
public void setPageFooter(final PageFooter footer)
{
if (footer == null)
{
throw new NullPointerException("AbstractReportDefinition.setPageFooter(...) : null not permitted.");
}
validateLooping(footer);
if (unregisterParent(footer))
{
return;
}
final Element oldElement = this.pageFooter;
this.pageFooter.setParent(null);
this.pageFooter = footer;
this.pageFooter.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(footer);
}
/**
* Returns the page footer.
*
* @return the page footer (never <code>null</code>).
*/
public PageFooter getPageFooter()
{
return pageFooter;
}
/**
* Sets the watermark band for the report.
*
* @param band the new watermark band (<code>null</code> not permitted).
*/
public void setWatermark(final Watermark band)
{
if (band == null)
{
throw new NullPointerException("AbstractReportDefinition.setWatermark(...) : null not permitted.");
}
validateLooping(band);
if (unregisterParent(band))
{
return;
}
final Element oldElement = this.watermark;
this.watermark.setParent(null);
this.watermark = band;
this.watermark.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(band);
}
/**
* Returns the report's watermark band.
*
* @return the watermark band (never <code>null</code>).
*/
public NoDataBand getNoDataBand()
{
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
return dataBody.getNoDataBand();
}
/**
* Sets the watermark band for the report.
*
* @param band the new watermark band (<code>null</code> not permitted).
*/
public void setNoDataBand(final NoDataBand band)
{
if (band == null)
{
throw new NullPointerException("AbstractReportDefinition.setNoDataBand(...) : null not permitted.");
}
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
dataBody.setNoDataBand(band);
}
/**
* Returns the report's watermark band.
*
* @return the watermark band (never <code>null</code>).
*/
public Watermark getWatermark()
{
return this.watermark;
}
/**
* Sets the item band for the report.
*
* @param band the new item band (<code>null</code> not permitted).
*/
public void setItemBand(final ItemBand band)
{
if (band == null)
{
throw new NullPointerException("AbstractReportDefinition.setItemBand(...) : null not permitted.");
}
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
dataBody.setItemBand(band);
}
/**
* Returns the report's item band.
*
* @return the item band (never <code>null</code>).
*/
public ItemBand getItemBand()
{
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
return dataBody.getItemBand();
}
/**
* Sets the item band for the report.
*
* @param band the new item band (<code>null</code> not permitted).
*/
public void setDetailsHeader(final DetailsHeader band)
{
if (band == null)
{
throw new NullPointerException("AbstractReportDefinition.setDetailsHeader(...) : null not permitted.");
}
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
dataBody.setDetailsHeader(band);
}
/**
* Returns the details header band.
*
* @return The details header band.
*/
public DetailsHeader getDetailsHeader()
{
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
return dataBody.getDetailsHeader();
}
/**
* Sets the item band for the report.
*
* @param band the new item band (<code>null</code> not permitted).
*/
public void setDetailsFooter(final DetailsFooter band)
{
if (band == null)
{
throw new NullPointerException("AbstractReportDefinition.setDetailsFooter(...) : null not permitted.");
}
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
dataBody.setDetailsFooter(band);
}
/**
* Returns the details header band.
*
* @return The details header band.
*/
public DetailsFooter getDetailsFooter()
{
final Group group = getInnerMostGroup();
final GroupDataBody dataBody = (GroupDataBody) group.getBody();
return dataBody.getDetailsFooter();
}
private Group getInnerMostGroup()
{
Group existingGroup = rootGroup;
GroupBody gb = existingGroup.getBody();
while (gb != null)
{
final int count = gb.getElementCount();
GroupBody locatedBody = null;
for (int i = 0; i < count; i++)
{
final ReportElement element = gb.getElement(i);
if (element instanceof Group)
{
existingGroup = (Group) element;
locatedBody = existingGroup.getBody();
break;
}
}
if (locatedBody == null)
{
gb = null;
}
else
{
gb = locatedBody;
}
}
return existingGroup;
}
private Group getInnerMostRelationalGroup()
{
Group existingGroup = rootGroup;
GroupBody gb = existingGroup.getBody();
while (gb != null)
{
final int count = gb.getElementCount();
boolean found = false;
for (int i = 0; i < count; i++)
{
final ReportElement element = gb.getElement(i);
if (element instanceof RelationalGroup)
{
existingGroup = (Group) element;
gb = existingGroup.getBody();
found = true;
break;
}
}
if (found == false)
{
gb = null;
}
}
return existingGroup;
}
/**
* Adds a group to the report. This replaces the group body on the group with a new data-group-body composed of the
* existing itemband and no-databand.
*
* @param group the group.
*/
public void addGroup(final RelationalGroup group)
{
if (group == null)
{
throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
}
final Group existingGroup = getInnerMostRelationalGroup();
final GroupBody gb = existingGroup.getBody();
existingGroup.setBody(new SubGroupBody(group));
group.setBody(gb);
}
/**
* Adds a crosstab group. This replaces any existing crosstabs and all the details sections.
*
* @param group
*/
public void addGroup(final CrosstabGroup group)
{
if (group == null)
{
throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
}
final Group existingGroup = getInnerMostRelationalGroup();
existingGroup.setBody(new SubGroupBody(group));
}
public void removeGroup(final CrosstabGroup group)
{
if (group == null)
{
throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
}
if (rootGroup == group)
{
removeRootGroup();
return;
}
final Group existingGroup = getInnerMostRelationalGroup();
final GroupBody gb = existingGroup.getBody();
if (gb instanceof SubGroupBody)
{
final SubGroupBody sgb = (SubGroupBody) gb;
if (sgb.getGroup() == group)
{
existingGroup.setBody(new GroupDataBody());
}
}
}
public void removeGroup(final RelationalGroup deleteGroup)
{
// Checks if we have a group to remove if not then throw an exception
if (deleteGroup == null)
{
throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
}
// Special case check to see if we're removing the root group.
if (rootGroup == deleteGroup) // If we're at root then
{
removeRootGroup(); // Remove it an exit
return;
}
// Walk through the groups and find the one that we need to remove
Group currentGroup = rootGroup;
Group parentGroup = null;
GroupBody currentGroupBody = currentGroup.getBody();
SubGroupBody sgb = null;
while (currentGroupBody instanceof SubGroupBody && currentGroup != deleteGroup)
{
parentGroup = currentGroup;
sgb = (SubGroupBody) currentGroupBody;
currentGroup = sgb.getGroup();
currentGroupBody = currentGroup.getBody();
}
if (currentGroup == deleteGroup) // if this is true then we found the group we need to remove
{
parentGroup.setBody(currentGroupBody);
final SubGroupBody subGroupBody = (SubGroupBody) currentGroup.getParentSection();
subGroupBody.setParent(parentGroup);
}
}
private void removeRootGroup()
{
final Group group = rootGroup;
final GroupBody rootBody = rootGroup.getBody();
if (group instanceof CrosstabGroup)
{
rootGroup = new CrosstabGroup();
rootGroup.setBody(rootBody);
}
else
{
if (rootBody instanceof SubGroupBody)
{
final SubGroupBody newRootGroup = (SubGroupBody) rootBody;
rootGroup.removeElement(rootBody);
rootGroup = newRootGroup.getGroup();
registerAsChild(rootGroup);
}
else
{
rootGroup = new RelationalGroup();
rootGroup.setBody(rootBody);
}
}
unregisterAsChild(group);
registerAsChild(rootGroup);
notifyNodeChildRemoved(group);
notifyNodeChildAdded(rootGroup);
}
/**
* Sets the groups for this report. If no list (null) or an empty list is given, an default group is created. This
* default group contains no elements and starts at the first record of the data and ends on the last record.
*
* @param groupList the list of groups.
*/
public void setGroups(final GroupList groupList)
{
if (groupList == null)
{
throw new NullPointerException("GroupList must not be null");
}
final ItemBand ib = getItemBand();
final NoDataBand nd = getNoDataBand();
final DetailsHeader detailsHeader = getDetailsHeader();
final DetailsFooter detailsFooter = getDetailsFooter();
final Group newRootGroup = groupList.constructRootGroup();
if (this.rootGroup == newRootGroup)
{
return;
}
validateLooping(newRootGroup);
if (unregisterParent(newRootGroup))
{
return;
}
final Element oldElement = this.rootGroup;
this.rootGroup.setParent(null);
this.rootGroup = newRootGroup;
this.rootGroup.setParent(this);
notifyNodeChildRemoved(oldElement);
notifyNodeChildAdded(rootGroup);
setDetailsFooter(detailsFooter);
setDetailsHeader(detailsHeader);
setItemBand(ib);
setNoDataBand(nd);
}
/**
* Returns the number of groups in this report. <P> Every report has at least one group defined.
*
* @return the group count.
*/
public int getGroupCount()
{
int result = 1; // we always have at least a default-group.
Group existingGroup = rootGroup;
GroupBody gb = existingGroup.getBody();
while (gb != null)
{
final int count = gb.getElementCount();
boolean found = false;
for (int i = 0; i < count; i++)
{
final ReportElement element = gb.getElement(i);
if (element instanceof Group)
{
existingGroup = (Group) element;
result += 1;
gb = existingGroup.getBody();
found = true;
break;
}
}
if (found == false)
{
gb = null;
}
}
return result;
}
/**
* Returns the group at the specified index or null, if there is no such group.
*
* @param groupIndex the group index.
* @return the requested group.
* @throws IllegalArgumentException if the count is negative.
* @throws IndexOutOfBoundsException if the count is greater than the number of defined groups.
*/
public Group getGroup(final int groupIndex)
{
if (groupIndex < 0)
{
throw new IllegalArgumentException("GroupCount must not be negative");
}
if (groupIndex == 0)
{
return rootGroup;
}
int result = 0; // we always have at least a default-group.
Group existingGroup = rootGroup;
GroupBody gb = existingGroup.getBody();
while (gb != null)
{
final int count = gb.getElementCount();
boolean found = false;
for (int i = 0; i < count; i++)
{
final ReportElement element = gb.getElement(i);
if (element instanceof Group)
{
existingGroup = (Group) element;
result += 1;
if (result == groupIndex)
{
return existingGroup;
}
gb = existingGroup.getBody();
found = true;
break;
}
}
if (found == false)
{
gb = null;
}
}
throw new IndexOutOfBoundsException("No group defined at the given index. Max-index=" + result);
}
/**
* Searches a group by its defined name. This method returns null, if the group was not found.
*
* @param name the name of the group.
* @return the group or null if not found.
*/
public RelationalGroup getGroupByName(final String name)
{
if (name == null)
{
throw new NullPointerException("AbstractReporDefinition.getGroupByName(..) : Null not permitted");
}
if (rootGroup instanceof RelationalGroup == false)
{
return null;
}
if (rootGroup.getName().equals(name))
{
return (RelationalGroup) rootGroup;
}
GroupBody gb = rootGroup.getBody();
while (gb instanceof SubGroupBody)
{
final SubGroupBody sgb = (SubGroupBody) gb;
final Group group = sgb.getGroup();
if (group instanceof RelationalGroup == false)
{
return null;
}
if (name.equals(group.getName()))
{
return (RelationalGroup) group;
}
gb = group.getBody();
}
return null;
}
/**
* Adds a function to the report's collection of expressions.
*
* @param function the function.
*/
public void addExpression(final Expression function)
{
if (function == null)
{
throw new NullPointerException("AbstractReporDefinition.addExpression(..) : Null not permitted");
}
expressions.add(function);
notifyNodeChildAdded(function);
}
public int getQueryTimeout()
{
final Object queryTimeoutText =
getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY_TIMEOUT);
if (queryTimeoutText instanceof Number)
{
final Number n = (Number) queryTimeoutText;
return n.intValue();
}
return 0;
}
public void setQueryTimeout(final int queryTimeout)
{
setAttribute(AttributeNames.Internal.NAMESPACE,
AttributeNames.Internal.QUERY_TIMEOUT, IntegerCache.getInteger(queryTimeout));
}
public int getQueryLimit()
{
final Object queryLimitText =
getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY_LIMIT);
if (queryLimitText instanceof Number)
{
final Number n = (Number) queryLimitText;
return n.intValue();
}
return -1;
}
public void setQueryLimit(final int queryLimit)
{
setAttribute(AttributeNames.Internal.NAMESPACE,
AttributeNames.Internal.QUERY_LIMIT, IntegerCache.getInteger(queryLimit));
}
/**
* Returns a new query or query-name that is used when retrieving the data from the data-factory.
*
* @return the query-string.
*/
public String getQuery()
{
return (String) getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY);
}
/**
* Defines a new query or query-name that is used when retrieving the data from the data-factory.
*
* @param query the query-string.
* @see DataFactory#queryData(String,DataRow)
*/
public void setQuery(final String query)
{
setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY, query);
}
/**
* Returns the stylesheet collection of this report. The stylesheet collection is fixed for the report and all
* elements of the report. When a band or group is added to the report it will get registered with this stylesheet
* collection and cannot be used in an different report.
*
* @return the stylesheet collection of the report, never null.
*/
public StyleSheetCollection getStyleSheetCollection()
{
return styleSheetCollection;
}
/**
* Returns the current datarow assigned to this report definition. JFreeReport objects do not hold a working DataRow,
* as the final contents of the data cannot be known, until the reporting has started.
*
* @return the default implementation for non-processed reports.
*/
public DataRow getDataRow()
{
return new ParameterDataRow(properties);
}
/**
* Returns the expressions for the report. When adding or removing expressions on the list, make sure to call
* "notifyStructureChanged" afterwards when in design-mode.
*
* @return the expressions.
*/
public ExpressionCollection getExpressions()
{
return expressions;
}
/**
* Sets the expressions for the report.
*
* @param expressions the expressions (<code>null</code> not permitted).
*/
public void setExpressions(final ExpressionCollection expressions)
{
if (expressions == null)
{
throw new NullPointerException("AbstractReportDefinition.setExpressions(...) : null not permitted.");
}
this.expressions = expressions;
notifyNodeStructureChanged();
}
/**
* Clones the report.
*
* @return the clone.
* @throws CloneNotSupportedException this should never happen.
*/
public Object clone()
throws CloneNotSupportedException
{
final AbstractReportDefinition report = (AbstractReportDefinition) super.clone();
report.eventListeners = null;
report.rootGroup = (Group) rootGroup.clone();
report.watermark = (Watermark) watermark.clone();
report.pageFooter = (PageFooter) pageFooter.clone();
report.pageHeader = (PageHeader) pageHeader.clone();
report.reportFooter = (ReportFooter) reportFooter.clone();
report.reportHeader = (ReportHeader) reportHeader.clone();
report.properties = (ReportProperties) properties.clone();
report.expressions = (ExpressionCollection) expressions.clone();
report.styleSheetCollection = (StyleSheetCollection) styleSheetCollection.clone();
report.dataSchemaDefinition = (DataSchemaDefinition) dataSchemaDefinition.clone();
report.rootGroup.setParent(report);
report.reportHeader.setParent(report);
report.reportFooter.setParent(report);
report.pageHeader.setParent(report);
report.pageFooter.setParent(report);
report.watermark.setParent(report);
final ReportPreProcessor[] reportPreProcessors = report.getPreProcessors();
for (int i = 0; i < reportPreProcessors.length; i++)
{
reportPreProcessors[i] = (ReportPreProcessor) reportPreProcessors[i].clone();
}
report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, reportPreProcessors);
final StructureFunction[] structureFunctions = report.getStructureFunctions();
for (int i = 0; i < structureFunctions.length; i++)
{
structureFunctions[i] = (StructureFunction) structureFunctions[i].clone();
}
report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, structureFunctions);
report.addReportModelListener(new ResourceBundleChangeHandler());
return report;
}
public Element derive()
throws CloneNotSupportedException
{
final AbstractReportDefinition report = (AbstractReportDefinition) super.derive();
report.eventListeners = null;
report.rootGroup = (Group) rootGroup.derive();
report.watermark = (Watermark) watermark.derive();
report.pageFooter = (PageFooter) pageFooter.derive();
report.pageHeader = (PageHeader) pageHeader.derive();
report.reportFooter = (ReportFooter) reportFooter.derive();
report.reportHeader = (ReportHeader) reportHeader.derive();
report.properties = (ReportProperties) properties.clone();
report.expressions = (ExpressionCollection) expressions.clone();
report.styleSheetCollection = (StyleSheetCollection) styleSheetCollection.clone();
report.dataSchemaDefinition = (DataSchemaDefinition) dataSchemaDefinition.clone();
report.rootGroup.setParent(report);
report.reportHeader.setParent(report);
report.reportFooter.setParent(report);
report.pageHeader.setParent(report);
report.pageFooter.setParent(report);
report.watermark.setParent(report);
final ReportPreProcessor[] reportPreProcessors = report.getPreProcessors();
for (int i = 0; i < reportPreProcessors.length; i++)
{
reportPreProcessors[i] = (ReportPreProcessor) reportPreProcessors[i].clone();
}
report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, reportPreProcessors);
final StructureFunction[] structureFunctions = report.getStructureFunctions();
for (int i = 0; i < structureFunctions.length; i++)
{
structureFunctions[i] = (StructureFunction) structureFunctions[i].getInstance();
}
report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, structureFunctions);
report.addReportModelListener(new ResourceBundleChangeHandler());
return report;
}
public void setElementAt(final int position, final Element element)
{
switch (position)
{
case 0:
setPageHeader((PageHeader) element);
break;
case 1:
setReportHeader((ReportHeader) element);
break;
case 2:
setRootGroup((Group) element);
break;
case 3:
setReportFooter((ReportFooter) element);
break;
case 4:
setPageFooter((PageFooter) element);
break;
case 5:
setWatermark((Watermark) element);
break;
default:
throw new IndexOutOfBoundsException();
}
}
protected void removeElement(final Element element)
{
if (element == null)
{
throw new NullPointerException("AbstractReporDefinition.removeElement(..) : Null not permitted");
}
if (pageHeader == element)
{
this.pageHeader.setParent(null);
this.pageHeader = new PageHeader();
this.pageHeader.setParent(this);
notifyNodeChildRemoved(element);
notifyNodeChildAdded(this.pageHeader);
}
else if (watermark == element)
{
this.watermark.setParent(null);
this.watermark = new Watermark();
this.watermark.setParent(this);
notifyNodeChildRemoved(element);
notifyNodeChildAdded(this.watermark);
}
else if (reportHeader == element)
{
this.reportHeader.setParent(null);
this.reportHeader = new ReportHeader();
this.reportHeader.setParent(this);
notifyNodeChildRemoved(element);
notifyNodeChildAdded(this.reportHeader);
}
else if (rootGroup == element)
{
removeRootGroup();
}
else if (reportFooter == element)
{
this.reportFooter.setParent(null);
this.reportFooter = new ReportFooter();
this.reportFooter.setParent(this);
notifyNodeChildRemoved(element);
notifyNodeChildAdded(this.reportFooter);
}
else if (pageFooter == element)
{
this.pageFooter.setParent(null);
this.pageFooter = new PageFooter();
this.pageFooter.setParent(this);
notifyNodeChildRemoved(element);
notifyNodeChildAdded(this.pageFooter);
}
}
public int getElementCount()
{
return 6;
}
public ReportElement getElement(final int index)
{
switch (index)
{
case 0:
return pageHeader;
case 1:
return reportHeader;
case 2:
return rootGroup;
case 3:
return reportFooter;
case 4:
return pageFooter;
case 5:
return watermark;
default:
throw new IndexOutOfBoundsException();
}
}
/**
* Defines the content base for the report. The content base will be used to resolve relative URLs during the report
* generation and resource loading. If there is no content base defined, it will be impossible to resolve relative
* paths.
*
* @param key the content base or null.
*/
public void setContentBase(final ResourceKey key)
{
setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.CONTENT_BASE, key);
}
/**
* Returns the content base of this report. The content base is used to resolve relative URLs during the report
* generation and resource loading. If there is no content base defined, it will be impossible to resolve relative
* paths.
*
* @return the content base or null, if no content base is defined.
*/
public ResourceKey getContentBase()
{
final Object attribute = getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.CONTENT_BASE);
if (attribute instanceof ResourceKey)
{
return (ResourceKey) attribute;
}
return getDefinitionSource();
}
public void setDefinitionSource(final ResourceKey key)
{
setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.SOURCE, key);
}
public ResourceKey getDefinitionSource()
{
final Object attribute = getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.SOURCE);
if (attribute instanceof ResourceKey)
{
return (ResourceKey) attribute;
}
return null;
}
/**
* Returns the currently assigned report definition.
*
* @return the report definition or null, if no report has been assigned.
*/
public ReportDefinition getReportDefinition()
{
return this;
}
/**
* Returns the data factory that has been assigned to this report. The data factory will never be null.
*
* @return the data factory.
*/
public abstract DataFactory getDataFactory();
/**
* Sets the data factory for the report.
*
* @param dataFactory the data factory for the report, never null.
*/
public abstract void setDataFactory(final DataFactory dataFactory);
public void addReportModelListener(final ReportModelListener listener)
{
if (eventListeners == null)
{
eventListeners = new EventListenerList();
}
this.eventListeners.add(ReportModelListener.class, listener);
}
public void removeReportModelListener(final ReportModelListener listener)
{
if (eventListeners == null)
{
return;
}
eventListeners.remove(ReportModelListener.class, listener);
}
public void fireModelLayoutChanged(final ReportElement node, final int type, final Object parameter)
{
if (node == this)
{
if (parameter instanceof Change == false)
{
nonVisualsChangeTracker += 1;
}
if (parameter instanceof DataFactory)
{
datasourceChangeTracker += 1;
}
}
updateInternalChangeFlag();
if (eventListeners != null)
{
final ReportModelEvent event = new ReportModelEvent(this, node, type, parameter);
final ReportModelListener[] listeners = (ReportModelListener[])
eventListeners.getListeners(ReportModelListener.class);
for (int i = 0; i < listeners.length; i++)
{
final ReportModelListener listener = listeners[i];
listener.nodeChanged(event);
}
}
}
public long getDatasourceChangeTracker()
{
return datasourceChangeTracker;
}
public long getNonVisualsChangeTracker()
{
return nonVisualsChangeTracker;
}
public void removeExpression(final Expression expression)
{
expressions.removeExpression(expression);
notifyNodeChildRemoved(expression);
}
public DataSchemaDefinition getDataSchemaDefinition()
{
return dataSchemaDefinition;
}
public void setDataSchemaDefinition(final DataSchemaDefinition dataSchemaDefinition)
{
if (dataSchemaDefinition == null)
{
throw new NullPointerException();
}
this.dataSchemaDefinition = dataSchemaDefinition;
notifyNodePropertiesChanged();
}
public abstract ResourceManager getResourceManager();
/**
* Adds a structural function to the report. Structural functions perform content preparation and maintainance
* operations before elements are layouted or printed.
* <p/>
* Structural function can live on their own processing level and are evaluated after the user expressions but before
* the layout expressions have been evaluated.
*
* @param function the structure function.
*/
public void addStructureFunction(final StructureFunction function)
{
if (function == null)
{
throw new NullPointerException();
}
final StructureFunction[] structureFunctions = getStructureFunctions();
final ArrayList newProcessors = new ArrayList(Math.max(10, structureFunctions.length));
for (int i = 0; i < structureFunctions.length; i++)
{
final StructureFunction structureFunction = structureFunctions[i];
newProcessors.add(structureFunction);
}
newProcessors.add(function);
final StructureFunction[] newArray =
(StructureFunction[]) newProcessors.toArray(new StructureFunction[newProcessors.size()]);
setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, newArray);
}
/**
* Returns the number of structural functions added to the report.
*
* @return the function count.
*/
public int getStructureFunctionCount()
{
final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS);
if (maybeArray instanceof StructureFunction[])
{
final StructureFunction[] structureFunctions = (StructureFunction[]) maybeArray;
return structureFunctions.length;
}
return 0;
}
/**
* Returns the structure function at the given position.
*
* @param index the position of the function in the list.
* @return the function, never null.
* @throws IndexOutOfBoundsException if the index is invalid.
*/
public StructureFunction getStructureFunction(final int index)
{
final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS);
if (maybeArray instanceof StructureFunction[])
{
final StructureFunction[] structureFunctions = (StructureFunction[]) maybeArray;
return structureFunctions[index];
}
throw new IndexOutOfBoundsException();
}
/**
* Removes the given function from the collection of structure functions. This removes only the first occurence of the
* function, in case a function has been added twice.
*
* @param f the function to be removed.
*/
public void removeStructureFunction(final StructureFunction f)
{
if (f == null)
{
throw new NullPointerException();
}
final StructureFunction[] structureFunctions = getStructureFunctions();
final ArrayList newProcessors = new ArrayList(Math.max(10, structureFunctions.length));
boolean found = false;
for (int i = 0; i < structureFunctions.length; i++)
{
final StructureFunction structureFunction = structureFunctions[i];
if (found || structureFunction != f)
{
newProcessors.add(structureFunction);
found = true;
}
}
if (found)
{
final StructureFunction[] newArray =
(StructureFunction[]) newProcessors.toArray(new StructureFunction[newProcessors.size()]);
setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, newArray);
}
}
/**
* Returns a copy of all structure functions contained in the report. Modifying the function instances will not alter
* the functions contained in the report.
*
* @return the functions.
*/
public StructureFunction[] getStructureFunctions()
{
final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS);
if (maybeArray instanceof StructureFunction[])
{
final StructureFunction[] structureFunctions = (StructureFunction[]) maybeArray;
return (StructureFunction[]) structureFunctions.clone();
}
return new StructureFunction[0];
}
/**
* A helper method that deserializes a object from the given stream.
*
* @param stream the stream from which to read the object data.
* @throws IOException if an IO error occured.
* @throws ClassNotFoundException if an referenced class cannot be found.
*/
private void readObject(final ObjectInputStream stream)
throws IOException, ClassNotFoundException
{
stream.defaultReadObject();
updateResourceBundleFactory();
}
protected void updateResourceBundleFactory()
{
if (resourceBundleFactory instanceof ExtendedResourceBundleFactory)
{
final ExtendedResourceBundleFactory erbf = (ExtendedResourceBundleFactory) resourceBundleFactory;
erbf.setResourceLoader(getResourceManager(), getContentBase());
}
}
/**
* Listens for changes to the DocumentBundle being used by a report and will update the ResourceManager to use that
* DocumentBundle.
*/
private static class ResourceBundleChangeHandler implements ReportModelListener
{
private ResourceBundleChangeHandler()
{
}
public void nodeChanged(final ReportModelEvent event)
{
final Object element = event.getElement();
if (element instanceof AbstractReportDefinition == false)
{
return;
}
if (event.isNodeStructureChanged())
{
return;
}
if (event.getParameter() instanceof StyleChange)
{
return;
}
final AbstractReportDefinition report = (AbstractReportDefinition) element;
report.updateResourceBundleFactory();
}
}
}