/* * 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 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core; 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.DesignTimeUtil; 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.filter.types.bands.DetailsFooterType; import org.pentaho.reporting.engine.classic.core.filter.types.bands.DetailsHeaderType; 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.ElementStyleSheet; import org.pentaho.reporting.engine.classic.core.style.ReportDefaultStyleSheet; import org.pentaho.reporting.engine.classic.core.util.InstanceID; import org.pentaho.reporting.engine.classic.core.util.IntegerCache; 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 * @noinspection UnusedDeclaration */ public abstract class AbstractReportDefinition extends Section implements ReportDefinition { /** * Storage for the expressions in the report. */ private ExpressionCollection expressions; /** * An hierarchy 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; 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.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 ); } /** * 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. */ @Deprecated public ResourceBundleFactory getResourceBundleFactory() { return DesignTimeUtil.getResourceBundleFactory( this ); } /** * 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. */ @Deprecated public void setResourceBundleFactory( final ResourceBundleFactory resourceBundleFactory ) { } 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 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<ReportPreProcessor> newProcessors = new ArrayList<ReportPreProcessor>( 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 = 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<ReportPreProcessor> newProcessors = new ArrayList<ReportPreProcessor>( 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 = 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 ); } /** * 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 Watermark getWatermark() { return this.watermark; } /** * Returns the report's no-data band. * * @return the no-data band (never <code>null</code>). */ public NoDataBand getNoDataBand() { Group innerMostRelationalGroup = getInnerMostRelationalGroup(); if ( innerMostRelationalGroup instanceof CrosstabGroup ) { CrosstabGroup cg = (CrosstabGroup) innerMostRelationalGroup; return cg.getNoDataBand(); } GroupBody body = innerMostRelationalGroup.getBody(); if ( body instanceof GroupDataBody ) { GroupDataBody gd = (GroupDataBody) body; return gd.getNoDataBand(); } return null; } /** * Returns the report's item band. * * @return the item band (never <code>null</code>). */ public ItemBand getItemBand() { final Group group = getInnerMostGroup(); final GroupBody body = group.getBody(); if ( body instanceof GroupDataBody ) { final GroupDataBody dataBody = (GroupDataBody) body; return dataBody.getItemBand(); } return null; } /** * Returns the details header band. * * @return The details header band. */ public DetailsHeader getDetailsHeader() { return (DetailsHeader) getInnerMostGroup().getChildElementByType( DetailsHeaderType.INSTANCE ); } /** * Returns the details header band. * * @return The details header band. */ public DetailsFooter getDetailsFooter() { return (DetailsFooter) getInnerMostGroup().getChildElementByType( DetailsFooterType.INSTANCE ); } private Group getInnerMostGroup() { Group existingGroup = rootGroup; while ( existingGroup != null ) { Group next = existingGroup.getBody().getGroup(); if ( next == null ) { return existingGroup; } existingGroup = next; } throw new IllegalStateException( "We shall never reach this point." ); } 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(); while ( currentGroupBody instanceof SubGroupBody && currentGroup != deleteGroup ) { parentGroup = currentGroup; final SubGroupBody 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 ); } /** * 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; } public RelationalGroup getRelationalGroup( final int groupIndex ) { final Group g = getGroup( groupIndex ); if ( g instanceof RelationalGroup ) { return (RelationalGroup) g; } return null; } /** * 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.matches( 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 ( group.matches( name ) ) { 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 ) ); } public int getUserQueryLimit() { final Object queryLimitText = getAttribute( AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY_LIMIT_USER ); if ( queryLimitText instanceof Number ) { final Number n = (Number) queryLimitText; return n.intValue(); } return -1; } public void setUserQueryLimit( final int queryLimit ) { setAttribute( AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY_LIMIT_USER, 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 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. */ public AbstractReportDefinition clone() { try { final AbstractReportDefinition report = (AbstractReportDefinition) super.clone(); report.eventListeners = null; report.rootGroup = 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.expressions = expressions.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] = 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 ); return report; } catch ( CloneNotSupportedException cne ) { throw new IllegalStateException( cne ); } } public AbstractReportDefinition derive( final boolean preserveElementInstanceIds ) { final AbstractReportDefinition report = (AbstractReportDefinition) super.derive( preserveElementInstanceIds ); report.eventListeners = null; report.rootGroup = rootGroup.derive( preserveElementInstanceIds ); report.watermark = (Watermark) watermark.derive( preserveElementInstanceIds ); report.pageFooter = (PageFooter) pageFooter.derive( preserveElementInstanceIds ); report.pageHeader = (PageHeader) pageHeader.derive( preserveElementInstanceIds ); report.reportFooter = (ReportFooter) reportFooter.derive( preserveElementInstanceIds ); report.reportHeader = (ReportHeader) reportHeader.derive( preserveElementInstanceIds ); report.expressions = expressions.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] = 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 ); 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 Element 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 = 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(); } /** * This method has only meaning for master-reports. This handle will be removed in the next majore release. * * @return */ @Deprecated 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<StructureFunction> newProcessors = new ArrayList<StructureFunction>( 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 = 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<StructureFunction> newProcessors = new ArrayList<StructureFunction>( 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 = 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 structureFunctions.clone(); } return new StructureFunction[0]; } public ElementStyleSheet getDefaultStyleSheet() { return ReportDefaultStyleSheet.getSectionDefault(); } public CrosstabCellBody getCrosstabCellBody() { final GroupBody body = getInnerMostGroup().getBody(); if ( body instanceof CrosstabCellBody ) { return (CrosstabCellBody) body; } return null; } public void setAutoSort( Boolean sort ) { setAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.AUTOSORT, sort ); } public Boolean getAutoSort() { Object attribute = getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.AUTOSORT ); if ( attribute instanceof Boolean ) { return (Boolean) attribute; } return null; } }