/* * 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 - 2016 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.designtime.AttributeChange; import org.pentaho.reporting.engine.classic.core.designtime.AttributeExpressionChange; import org.pentaho.reporting.engine.classic.core.designtime.StyleChange; import org.pentaho.reporting.engine.classic.core.designtime.StyleExpressionChange; import org.pentaho.reporting.engine.classic.core.dom.ReportStructureMatcher; import org.pentaho.reporting.engine.classic.core.event.ReportModelEvent; import org.pentaho.reporting.engine.classic.core.filter.DataSource; import org.pentaho.reporting.engine.classic.core.filter.DataTarget; import org.pentaho.reporting.engine.classic.core.filter.EmptyDataSource; import org.pentaho.reporting.engine.classic.core.filter.types.LegacyType; import org.pentaho.reporting.engine.classic.core.function.Expression; import org.pentaho.reporting.engine.classic.core.layout.style.SimpleStyleSheet; import org.pentaho.reporting.engine.classic.core.metadata.AttributeMetaData; import org.pentaho.reporting.engine.classic.core.metadata.ElementMetaData; import org.pentaho.reporting.engine.classic.core.metadata.ElementType; import org.pentaho.reporting.engine.classic.core.style.ElementDefaultStyleSheet; import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys; import org.pentaho.reporting.engine.classic.core.style.ElementStyleSheet; import org.pentaho.reporting.engine.classic.core.style.StyleKey; import org.pentaho.reporting.engine.classic.core.util.InstanceID; import org.pentaho.reporting.libraries.base.util.NullOutputStream; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import org.pentaho.reporting.libraries.resourceloader.ResourceException; import org.pentaho.reporting.libraries.resourceloader.ResourceKey; import org.pentaho.reporting.libraries.resourceloader.ResourceKeyUtils; import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import org.pentaho.reporting.libraries.serializer.SerializerHelper; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; /** * Base class for all report elements (displays items that can appear within a report band). * <p/> * Elements can either be Bands (which are container classes that group elements) or content-elements. All elements can * have a name and have a private style sheet defined. The style sheet is used to store and access all element * properties that can be used to control the layout of the element or affect the elements appearance in the generated * content. * <p/> * Elements can inherit all style information from its parent. A style value is inherited whenever the element's * stylesheet does not define an own value for the corresponding key. Some style-keys cannot be inherited at all, in * that case the default-style-sheets value is used as fall-back value. In addition to the bands stylesheet, elements * may also inherit from stylesheets assigned to the current report definition's StyleSheetCollection. Foreign * stylesheet will be lost after the local cloning is complete. * <p/> * All Style-computation is done outside of the element using one of the style-resolver implementations. * * @author David Gilbert * @author Thomas Morgner * @noinspection ClassReferencesSubclass */ public class Element implements DataTarget, ReportElement { private static final String[] EMPTY_NAMES = new String[0]; private static final Log logger = LogFactory.getLog( Element.class ); /** * An private implementation of a stylesheet. * <p/> * Using that stylesheet outside the element class will not work, cloning an element's private stylesheet without * cloning the element will produce <code>IllegalStateException</code>s later. */ private static class InternalElementStyleSheet extends ElementStyleSheet { /** * The element that contains this stylesheet. */ private Element element; /** * Creates a new internal stylesheet for the given element. * * @param element * the element * @throws NullPointerException * if the element given is null. */ protected InternalElementStyleSheet( final Element element ) { this.element = element; } /** * Returns the element for this stylesheet. * * @return the element. */ public Element getElement() { return element; } /** * Updates the reference to the element after the cloning. * * @param e * the element that contains this stylesheet. */ protected void updateElementReference( final Element e ) { if ( e == null ) { throw new NullPointerException( "Invalid implementation: Self reference cannot be null after cloning." ); } this.element = e; } public void setStyleProperty( final StyleKey key, final Object value ) { final long l = getChangeTracker(); final Object oldValue; if ( super.isLocalKey( key ) ) { oldValue = super.getStyleProperty( key ); } else { oldValue = null; } super.setStyleProperty( key, value ); if ( l != getChangeTracker() ) { element.notifyNodePropertiesChanged( new StyleChange( key, oldValue, value ) ); } } } /** * The internal constant to mark anonymous element names. */ public static final String ANONYMOUS_ELEMENT_PREFIX = "anonymousElement@"; /** * A null datasource. This class is immutable and shared across all elements. */ private static final DataSource NULL_DATASOURCE = new EmptyDataSource(); private DataSource datasource; /** * The stylesheet defines global appearance for elements. */ private InternalElementStyleSheet style; /** * the parent for the element (the band where the element is contained in). */ private Section parent; /** * The tree lock to identify the element. This object is shared among all clones and can be used to identify elements * with the same anchestor. */ private InstanceID treeLock; /** * The map of style-expressions keyed by the style-key. */ private HashMap<StyleKey, Expression> styleExpressions; private transient ReportAttributeMap<Object> attributes; private transient boolean copyOnWrite; private ReportAttributeMap<Expression> attributeExpressions; private transient ReportAttributeMap<Object> cachedAttributes; private transient long changeTracker; private transient Object elementContext; private transient ElementType elementType; /** * Constructs an element. * <p/> * The element inherits the element's defined default ElementStyleSheet to provide reasonable default values for * common stylekeys. When the element is added to the band, the bands stylesheet is set as parent to the element's * stylesheet. * <p/> * A datasource is assigned with this element is set to a default source, which always returns null. */ public Element() { treeLock = new InstanceID(); datasource = Element.NULL_DATASOURCE; style = new InternalElementStyleSheet( this ); attributes = new ReportAttributeMap<Object>(); setElementType( LegacyType.INSTANCE ); } protected Element( final InstanceID id ) { this(); treeLock = id; } public SimpleStyleSheet getComputedStyle() { final SimpleStyleSheet computedStyle = (SimpleStyleSheet) this.attributes.getAttribute( AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.COMPUTED_STYLE ); if ( computedStyle == null ) { final int hc = System.identityHashCode( this ); throw new InvalidReportStateException( "No computed style for (" + hc + ") - " + this ); } return computedStyle; } public void setComputedStyle( final SimpleStyleSheet computedStyle ) { if ( computedStyle == null ) { throw new IllegalArgumentException(); } setAttribute( AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.COMPUTED_STYLE, computedStyle, false ); } public void setAttributeExpression( final String namespace, final String name, final Expression value ) { if ( attributeExpressions == null ) { attributeExpressions = new ReportAttributeMap<Expression>(); } final Expression oldExpression = this.attributeExpressions.setAttribute( namespace, name, value ); notifyNodePropertiesChanged( new AttributeExpressionChange( namespace, name, oldExpression, value ) ); } public Expression getAttributeExpression( final String namespace, final String name ) { if ( attributeExpressions == null ) { return null; } return attributeExpressions.getAttribute( namespace, name ); } public String[] getAttributeExpressionNamespaces() { if ( attributeExpressions == null ) { return Element.EMPTY_NAMES; } return attributeExpressions.getNameSpaces(); } public String[] getAttributeExpressionNames( final String name ) { if ( attributeExpressions == null ) { return Element.EMPTY_NAMES; } return attributeExpressions.getNames( name ); } public ReportAttributeMap<Expression> getAttributeExpressions() { if ( attributeExpressions == null ) { attributeExpressions = new ReportAttributeMap<Expression>(); } return attributeExpressions; } public void setAttribute( final String namespace, final String name, final Object value ) { setAttribute( namespace, name, value, true ); } public void setAttribute( final String namespace, final String name, final Object value, final boolean notifyChange ) { if ( copyOnWrite ) { this.attributes = attributes.clone(); this.copyOnWrite = false; } final Object oldValue = attributes.setAttribute( namespace, name, value ); if ( cachedAttributes != null ) { if ( cachedAttributes.getChangeTracker() != attributes.getChangeTracker() ) { cachedAttributes = null; } } if ( AttributeNames.Core.NAMESPACE.equals( namespace ) && AttributeNames.Core.ELEMENT_TYPE.equals( name ) ) { if ( value instanceof ElementType ) { this.elementType = (ElementType) value; } else { this.elementType = LegacyType.INSTANCE; } } if ( notifyChange ) { notifyNodePropertiesChanged( new AttributeChange( namespace, name, oldValue, value ) ); } } public Object getAttribute( final String namespace, final String name ) { return attributes.getAttribute( namespace, name ); } public Object getFirstAttribute( final String localName ) { return attributes.getFirstAttribute( localName ); } public String[] getAttributeNamespaces() { return attributes.getNameSpaces(); } public String[] getAttributeNames( final String namespace ) { return attributes.getNames( namespace ); } /** * Returns the attributes of the element as unmodifable collection. The collection can be safely stored as it is * guaranteed to never change. (However, no assumptions are made about the contents inside the collection.) * * @return the unmodifiable attribute collection */ public ReportAttributeMap<Object> getAttributes() { if ( cachedAttributes != null ) { if ( cachedAttributes.getChangeTracker() == attributes.getChangeTracker() ) { return cachedAttributes; } } cachedAttributes = attributes.createUnmodifiableMap(); return cachedAttributes; } public <TS> TS getAttributeTyped( final String namespace, final String attribute, final Class<TS> filter ) { return attributes.getAttributeTyped( namespace, attribute, filter ); } public void setElementType( final ElementType elementType ) { if ( elementType == null ) { throw new NullPointerException( "Element.setElementType(..): ElementType cannot be null" ); } setAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE, elementType, true ); } public ElementType getElementType() { if ( elementType == null ) { final Object maybeElementType = getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE ); if ( maybeElementType instanceof ElementType ) { this.elementType = (ElementType) maybeElementType; } else { this.elementType = LegacyType.INSTANCE; } } return elementType; } public String getElementTypeName() { return getElementType().getMetaData().getName(); } public final ElementMetaData getMetaData() { return getElementType().getMetaData(); } /** * Return the parent of the Element. You can use this to explore the component tree. * * @return the parent of this element. */ public final Band getParent() { if ( parent instanceof Band ) { return (Band) parent; } return null; } public final Section getParentSection() { return parent; } /** * Defines the parent of the Element. * <p/> * This method is public as a implementation side effect. Only a band or section implementation should call this * method. Calling this method manually will create a huge disaster. * * @param parent * (null allowed). */ protected final void setParent( final Section parent ) { this.parent = parent; this.notifyElement(); } protected void notifyElement() { } /** * Defines the name for this Element. The name must not be empty, or a NullPointerException is thrown. * <p/> * Names can be used to lookup an element within a band. There is no requirement for element names to be unique. * * @param name * the name of this element */ public void setName( final String name ) { setAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.NAME, name ); } /** * Returns the name of the Element. The name of the Element is never null. * * @return the name. */ public String getName() { final Object o = getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.NAME ); if ( o != null ) { return String.valueOf( o ); } return ""; } /** * Returns the datasource for this Element. You cannot override this function as the Element needs always to be the * last consumer in the chain of filters. This function must never return null. * * @return the assigned legacy datasource. * @deprecated Whereever possible use ElementType implementations instead. This method only exists to let old reports * run. */ public final DataSource getDataSource() { return datasource; } /** * Sets the data source for this Element. The data source is used to produce or query the element's display value. * * @param ds * the datasource (<code>null</code> not permitted). * @throws NullPointerException * if the given data source is null. * @deprecated The data-source should not be used anymore. Use ElementType implementations instead. This method only * exists to let old reports run. */ public void setDataSource( final DataSource ds ) { if ( ds == null ) { throw new NullPointerException( "Element.setDataSource(...) : null data source." ); } this.datasource = ds; notifyNodePropertiesChanged(); } /** * Defines whether this Element should be painted. The detailed implementation is up to the outputtarget. * * @return the current visiblity state. */ public boolean isVisible() { return getStyle().getBooleanStyleProperty( ElementStyleKeys.VISIBLE, true ); } /** * Defines, whether this Element should be visible in the output. The interpretation of this flag is up to the content * processor. * * @param b * the new visibility state */ public void setVisible( final boolean b ) { if ( b ) { getStyle().setStyleProperty( ElementStyleKeys.VISIBLE, Boolean.TRUE ); } else { getStyle().setStyleProperty( ElementStyleKeys.VISIBLE, Boolean.FALSE ); } } /** * Clones this Element, the datasource and the private stylesheet of this Element. The clone does no longer have a * parent, as the old parent would not recognize that new object anymore. * * @return a clone of this Element. */ public Element clone() { try { final Element e = (Element) super.clone(); e.style = (InternalElementStyleSheet) style.clone(); e.datasource = datasource.clone(); e.parent = null; e.style.updateElementReference( e ); e.elementContext = null; if ( attributeExpressions != null ) { e.attributes = attributes.clone(); e.attributeExpressions = attributeExpressions.clone(); final String[] namespaces = e.attributeExpressions.getNameSpaces(); for ( int i = 0; i < namespaces.length; i++ ) { final String namespace = namespaces[i]; final Map<String, Expression> attrsNs = attributeExpressions.getAttributes( namespace ); for ( final Map.Entry<String, Expression> entry : attrsNs.entrySet() ) { final Expression exp = entry.getValue(); e.attributeExpressions.setAttribute( namespace, entry.getKey(), exp == null ? null : (Expression) exp.clone() ); } } } else { if ( e.cachedAttributes != null ) { e.attributes = attributes; e.copyOnWrite = true; copyOnWrite = true; } else { e.copyOnWrite = false; e.attributes = attributes.clone(); } } if ( styleExpressions != null ) { e.styleExpressions = (HashMap<StyleKey, Expression>) styleExpressions.clone(); for ( final Map.Entry<StyleKey, Expression> entry : e.styleExpressions.entrySet() ) { final Expression exp = entry.getValue(); entry.setValue( (Expression) exp.clone() ); } } return e; } catch ( CloneNotSupportedException cne ) { throw new IllegalStateException( cne ); } } public final Element derive() { return derive( false ); } /** * Creates a deep copy of this element and regenerates all instance-ids. * * @param preserveElementInstanceIds * defines whether this call generates new instance-ids for the derived elements. Instance-IDs are used by * the report processor to recognize reoccurring elements and must not changed within the report run. Outside * of the report processors new instance ids should be generated at all times to separate instances and to * make them uniquely identifiable. * @return the copy of the element. */ public Element derive( final boolean preserveElementInstanceIds ) { try { final Element e = (Element) super.clone(); e.elementContext = null; if ( preserveElementInstanceIds == false ) { e.treeLock = new InstanceID(); } e.style = (InternalElementStyleSheet) style.derive( preserveElementInstanceIds ); e.datasource = datasource.clone(); e.parent = null; e.style.updateElementReference( e ); e.attributes = attributes.clone(); e.copyOnWrite = false; final ElementMetaData metaData = e.getMetaData(); final String[] namespaces = e.attributes.getNameSpaces(); for ( int i = 0; i < namespaces.length; i++ ) { final String namespace = namespaces[i]; final Map attrsNs = attributes.getAttributes( namespace ); final Iterator it = attrsNs.entrySet().iterator(); while ( it.hasNext() ) { final Map.Entry entry = (Map.Entry) it.next(); final Object value = entry.getValue(); final String name = (String) entry.getKey(); final AttributeMetaData data = metaData.getAttributeDescription( namespace, name ); if ( data == null ) { if ( logger.isDebugEnabled() ) { logger.debug( getElementTypeName() + ": Attribute " + namespace + "|" + name + " is not listed in the metadata." ); } } if ( value instanceof Cloneable ) { e.attributes.setAttribute( namespace, name, ObjectUtilities.clone( value ) ); } else if ( data == null || data.isComputed() == false || data.isDesignTimeValue() ) { e.attributes.setAttribute( namespace, name, value ); } else { e.attributes.setAttribute( namespace, name, null ); } } } if ( e.cachedAttributes != null && e.attributes.getChangeTracker() != e.cachedAttributes.getChangeTracker() ) { e.cachedAttributes = null; } if ( attributeExpressions != null ) { e.attributeExpressions = attributeExpressions.clone(); final String[] attrExprNamespaces = e.attributeExpressions.getNameSpaces(); for ( int i = 0; i < attrExprNamespaces.length; i++ ) { final String namespace = attrExprNamespaces[i]; final Map attrsNs = attributeExpressions.getAttributes( namespace ); final Iterator it = attrsNs.entrySet().iterator(); while ( it.hasNext() ) { final Map.Entry entry = (Map.Entry) it.next(); final Expression exp = (Expression) entry.getValue(); e.attributeExpressions.setAttribute( namespace, (String) entry.getKey(), exp == null ? null : exp.getInstance() ); } } } if ( styleExpressions != null ) { // noinspection unchecked e.styleExpressions = (HashMap<StyleKey, Expression>) styleExpressions.clone(); final Iterator<Map.Entry<StyleKey, Expression>> styleExpressionsIt = e.styleExpressions.entrySet().iterator(); while ( styleExpressionsIt.hasNext() ) { final Map.Entry<StyleKey, Expression> entry = styleExpressionsIt.next(); final Expression exp = entry.getValue(); entry.setValue( exp.getInstance() ); } } return e; } catch ( CloneNotSupportedException cne ) { throw new IllegalStateException( cne ); } } /** * Returns this elements private stylesheet. This sheet can be used to override the default values set in one of the * parent-stylesheets. * * @return the Element's stylesheet */ public ElementStyleSheet getStyle() { return style; } /** * Returns the tree lock object for the self tree. If the element is part of a content hierarchy, the parent's tree * lock is returned. * * @return the treelock object. */ public final Object getTreeLock() { final Section parent = getParentSection(); if ( parent != null ) { return parent.getTreeLock(); } return treeLock; } /** * Returns the Xml-ID of this element. This ID is unique within the report-definition, but is not a internal * object-instance ID but a user-defined string. * * @return the element id. */ public String getId() { return (String) getAttribute( AttributeNames.Xml.NAMESPACE, AttributeNames.Xml.ID ); } /** * Defines the Xml-ID of this element. This ID is unique within the report-definition, but is not a internal * object-instance ID but a user-defined string. * * @param id * the element id. */ public void setId( final String id ) { setAttribute( AttributeNames.Xml.NAMESPACE, AttributeNames.Xml.ID, id ); } /** * Returns a unique identifier for the given instance. The identifier can be used to recognize cloned instance which * have the same anchestor. The identifier is unique as long as the element remains in the JVM, it does not guarantee * uniqueness or the ability to recognize clones, after the element has been serialized. * * @return the object identifier. */ public final InstanceID getObjectID() { return treeLock; } /** * Checks whether the layout of this element is dynamic and adjusts to the element's printable content. If set to * false, the element's minimum-size will be also used as maximum size. * * @return true, if the Element's layout is dynamic, false otherwise. */ public boolean isDynamicContent() { return getStyle().getBooleanStyleProperty( ElementStyleKeys.DYNAMIC_HEIGHT ); } /** * Defines the stylesheet property for the dynamic attribute. Calling this function with either parameter will * override any previously defined value for the dynamic attribute. The value can no longer be inherited from parent * stylesheets. * * @param dynamicContent * the new state of the dynamic flag. */ public void setDynamicContent( final boolean dynamicContent ) { getStyle().setBooleanStyleProperty( ElementStyleKeys.DYNAMIC_HEIGHT, dynamicContent ); } /** * Returns the currently assigned report definition. * * @return the report definition or null, if no report has been assigned. */ public ReportDefinition getReportDefinition() { if ( parent != null ) { return parent.getReportDefinition(); } return null; } /** * Returns the master-report element. This will be a MasterReport while outside of the report processing. Inside the * report processing (when called from a report-definition contained in a report-state), this will be a * ReportDefinitionImpl. * * @return the master report. */ public ReportDefinition getMasterReport() { if ( parent != null ) { return parent.getMasterReport(); } return null; } /** * Redefines the link target for this element. * * @param target * the target */ public void setHRefTarget( final String target ) { getStyle().setStyleProperty( ElementStyleKeys.HREF_TARGET, target ); } /** * Returns the currently set link target for this element. * * @return the link target. */ public String getHRefTarget() { return (String) getStyle().getStyleProperty( ElementStyleKeys.HREF_TARGET ); } /** * Creates the global stylesheet for this element type. The global stylesheet is an immutable stylesheet that provides * reasonable default values for some of the style keys. * <p/> * The global default stylesheet is always the last stylesheet that will be queried for values. * * @return the global stylesheet. */ public ElementStyleSheet getDefaultStyleSheet() { return ElementDefaultStyleSheet.getDefaultStyle(); } /** * Adds a function to the report's collection of expressions. * * @param property * the stylekey that will be modified by this element. * @param function * the function. */ public void setStyleExpression( final StyleKey property, final Expression function ) { if ( styleExpressions == null ) { if ( function == null ) { return; } styleExpressions = new HashMap<StyleKey, Expression>(); } final Object oldValue; if ( function == null ) { oldValue = styleExpressions.remove( property ); } else { oldValue = styleExpressions.put( property, function ); } notifyNodePropertiesChanged( new StyleExpressionChange( property, (Expression) oldValue, function ) ); } /** * Returns the expressions for the report. * * @param property * the stylekey for which an style-expression is returned. * @return the expressions. */ public Expression getStyleExpression( final StyleKey property ) { if ( styleExpressions == null ) { return null; } return styleExpressions.get( property ); } /** * Returns a map of all style expressions attached to this element. The map is keyed by an StyleKey and contains * Expression instances. * * @return the expression. */ public Map<StyleKey, Expression> getStyleExpressions() { if ( styleExpressions != null ) { return Collections.unmodifiableMap( styleExpressions ); } return Collections.emptyMap(); } /** * Returns the resource-key of the file that defined this element. This method may return null if the whole report was * created in memory. * * @return the the definition source. */ public ResourceKey getDefinitionSource() { final Object o = getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.SOURCE ); if ( o instanceof ResourceKey ) { return (ResourceKey) o; } if ( parent != null ) { return parent.getDefinitionSource(); } return null; } public ResourceKey getContentBase() { final Object o = getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.CONTENT_BASE ); if ( o instanceof ResourceKey ) { return (ResourceKey) o; } if ( parent != null ) { return parent.getContentBase(); } return null; } /** * Returns the element's change-tracker. The changetracker is a version indicator that tracks the number of changes * that have been made to an element and makes it easier to implement caching on top of elements or bands. Any change * will increase the change-tracker. * * @return the element's change tracking number. * @see Element#notifyNodePropertiesChanged() * @see Element#notifyNodeStructureChanged() */ public long getChangeTracker() { return changeTracker; } /** * Notifies the element and any parent element that a property of this element has changed. This notification updates * the change tracker. */ public void notifyNodePropertiesChanged() { updateChangedFlagInternal( this, ReportModelEvent.NODE_PROPERTIES_CHANGED, null ); } public void notifyNodePropertiesChanged( final Object parameter ) { updateChangedFlagInternal( this, ReportModelEvent.NODE_PROPERTIES_CHANGED, parameter ); } /** * Notifies the element and any parent element that a child node has been added. This notification updates the change * tracker. * * @param o * the node that has been added. */ public void notifyNodeChildAdded( final Object o ) { updateChangedFlagInternal( this, ReportModelEvent.NODE_ADDED, o ); } /** * Notifies the element and any parent element that a child node has been removed. This notification updates the * change tracker. * * @param o * the node that has been removed. */ public void notifyNodeChildRemoved( final Object o ) { updateChangedFlagInternal( this, ReportModelEvent.NODE_REMOVED, o ); } /** * Notifies the element and any parent element that the structure of this element has changed in some undisclosed way. * This notification updates the change tracker. */ public void notifyNodeStructureChanged() { updateChangedFlagInternal( this, ReportModelEvent.NODE_STRUCTURE_CHANGED, null ); } /** * Updates the change flag and notifies the parent, if this element has a parent. * * @param element * the element that caused the notification. * @param type * the notification type. * @param parameter * the optional parameter further describing the event. */ protected void updateChangedFlagInternal( final ReportElement element, final int type, final Object parameter ) { changeTracker += 1; if ( parent != null ) { parent.updateChangedFlagInternal( element, type, parameter ); } } /** * Updates the internal change flag without notifying the parent. This is a internal method and unless you are calling * this method from a report-definition, you are probably doing something wrong. */ protected final void updateInternalChangeFlag() { changeTracker += 1; } /** * This method is intended for subreport handling inside the process state. Messing with the change tracker in any * other way will break reports. You have been warned. This method is internal and may change or be renamed at any * time. * * @param changeTracker * the new change tracker value */ protected final void setChangeTracker( final long changeTracker ) { this.changeTracker = changeTracker; } /** * A helper method that serializes the element object. * * @param stream * the stream to which the element should be serialized. * @throws IOException * if an IO error occured or a property was not serializable. */ private void writeObject( final ObjectOutputStream stream ) throws IOException { stream.defaultWriteObject(); final ReportAttributeMap attributes = this.attributes; stream.writeLong( attributes.getChangeTracker() ); final String[] nameSpaces = attributes.getNameSpaces(); stream.writeObject( nameSpaces ); for ( int i = 0; i < nameSpaces.length; i++ ) { final String nameSpace = nameSpaces[i]; final String[] names = attributes.getNames( nameSpace ); stream.writeObject( names ); for ( int j = 0; j < names.length; j++ ) { final String name = names[j]; final Object attribute = attributes.getAttribute( nameSpace, name ); final AttributeMetaData data = getMetaData().getAttributeDescription( nameSpace, name ); if ( data != null ) { if ( data.isTransient() ) { stream.writeByte( 1 ); continue; } if ( attribute instanceof ResourceKey ) { final ResourceKey key = (ResourceKey) attribute; final ResourceKey parent = key.getParent(); if ( AttributeNames.Core.NAMESPACE.equals( nameSpace ) && ( AttributeNames.Core.CONTENT_BASE.equals( name ) || AttributeNames.Core.SOURCE.equals( name ) ) ) { if ( parent != null ) { // unwrap the content base attribute. After deserialization, the report assumes the bundle-location // as content base, as the bundle will be gone. if ( isKeySerializable( parent ) ) { stream.writeByte( 0 ); SerializerHelper.getInstance().writeObject( parent, stream ); } else { stream.writeByte( 1 ); } } else { // great, the report was never part of a bundle. That makes life easier and the key should be // safely serializable too. if ( isKeySerializable( key ) ) { stream.writeByte( 0 ); SerializerHelper.getInstance().writeObject( key, stream ); } else { stream.writeByte( 1 ); } } } else { if ( "Resource".equals( data.getValueRole() ) || parent != null ) { stream.writeByte( 0 ); try { final ResourceKey resourceKey = ResourceKeyUtils.embedResourceInKey( locateResourceManager(), key, key.getFactoryParameters() ); SerializerHelper.getInstance().writeObject( resourceKey, stream ); } catch ( ResourceException e ) { throw new IOException( "Failed to convert resource-key into byte-array key: " + e ); } } else { stream.writeByte( 0 ); SerializerHelper.getInstance().writeObject( attribute, stream ); } } } else if ( SerializerHelper.getInstance().isSerializable( attribute ) ) { stream.writeByte( 0 ); SerializerHelper.getInstance().writeObject( attribute, stream ); } else { stream.writeByte( 1 ); } } else if ( attribute instanceof String ) { stream.writeByte( 0 ); SerializerHelper.getInstance().writeObject( attribute, stream ); } else { stream.writeByte( 1 ); } } } } private boolean isKeySerializable( final ResourceKey key ) { try { final ObjectOutputStream oout = new ObjectOutputStream( new NullOutputStream() ); oout.writeObject( key ); oout.close(); return true; } catch ( Exception e ) { return false; } } private ResourceManager locateResourceManager() { final ReportDefinition report = getMasterReport(); if ( report instanceof MasterReport ) { MasterReport mr = (MasterReport) report; return mr.getResourceManager(); } return new ResourceManager(); } /** * 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(); this.attributes = new ReportAttributeMap<Object>( stream.readLong() ); final String[] nameSpaces = (String[]) stream.readObject(); for ( int i = 0; i < nameSpaces.length; i++ ) { final String nameSpace = nameSpaces[i]; final String[] names = (String[]) stream.readObject(); for ( int j = 0; j < names.length; j++ ) { final String name = names[j]; final int nullHandler = stream.readByte(); if ( nullHandler == 0 ) { final Object attribute = SerializerHelper.getInstance().readObject( stream ); this.attributes.setAttribute( nameSpace, name, attribute ); } } } } /** * Returns a string representation of the band, useful mainly for debugging purposes. * * @return a string representation of this band. */ public String toString() { final StringBuilder b = new StringBuilder( 100 ); b.append( this.getClass().getName() ); b.append( "={name=\"" ); b.append( getName() ); b.append( "\", type=\"" ); b.append( getElementTypeName() ); b.append( "\"}" ); return b.toString(); } public ReportElement[] getChildElementsByType( final ElementType type ) { return ReportStructureMatcher.findElementsByType( this, type ); } public ReportElement getChildElementByType( final ElementType type ) { return ReportStructureMatcher.findElementByType( this, type ); } public ReportElement[] getChildElementsByName( final String name ) { return ReportStructureMatcher.findElementsByName( this, name ); } public void copyInto( final Element target ) { final ElementMetaData metaData = getMetaData(); final String[] attributeNamespaces = getAttributeNamespaces(); for ( int i = 0; i < attributeNamespaces.length; i++ ) { final String namespace = attributeNamespaces[i]; final String[] attributeNames = getAttributeNames( namespace ); for ( int j = 0; j < attributeNames.length; j++ ) { final String name = attributeNames[j]; final AttributeMetaData attributeDescription = metaData.getAttributeDescription( namespace, name ); if ( attributeDescription == null ) { continue; } if ( attributeDescription.isTransient() ) { continue; } if ( attributeDescription.isComputed() ) { continue; } if ( AttributeNames.Core.ELEMENT_TYPE.equals( name ) && AttributeNames.Core.NAMESPACE.equals( namespace ) ) { continue; } target.setAttribute( namespace, name, getAttribute( namespace, name ), false ); } } final String[] attrExprNamespaces = getAttributeExpressionNamespaces(); for ( int i = 0; i < attrExprNamespaces.length; i++ ) { final String namespace = attrExprNamespaces[i]; final String[] attributeNames = getAttributeExpressionNames( namespace ); for ( int j = 0; j < attributeNames.length; j++ ) { final String name = attributeNames[j]; final AttributeMetaData attributeDescription = metaData.getAttributeDescription( namespace, name ); if ( attributeDescription == null ) { continue; } if ( attributeDescription.isTransient() ) { continue; } target.setAttributeExpression( namespace, name, getAttributeExpression( namespace, name ) ); } } final ElementStyleSheet styleSheet = getStyle(); final StyleKey[] styleKeys = styleSheet.getDefinedPropertyNamesArray(); for ( int i = 0; i < styleKeys.length; i++ ) { final StyleKey styleKey = styleKeys[i]; if ( styleKey != null ) { target.getStyle().setStyleProperty( styleKey, styleSheet.getStyleProperty( styleKey ) ); } } final Set<Map.Entry<StyleKey, Expression>> styleExpressionEntries = getStyleExpressions().entrySet(); for ( final Map.Entry<StyleKey, Expression> entry : styleExpressionEntries ) { target.setStyleExpression( entry.getKey(), entry.getValue() ); } } public <T> T getElementContext( final Class<T> contextType ) { if ( contextType.isInstance( elementContext ) ) { return contextType.cast( elementContext ); } try { final T elementContext = contextType.newInstance(); this.elementContext = elementContext; return elementContext; } catch ( Exception e ) { throw new InvalidReportStateException( "Unable to create element context of " + contextType, e ); } } public void copyAttributes( final ReportAttributeMap<Object> attributes ) { // noinspection unchecked this.attributes.putAll( attributes ); this.cachedAttributes = null; final Object value = attributes.getAttribute( AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE ); if ( value instanceof ElementType ) { this.elementType = (ElementType) value; } else { this.elementType = LegacyType.INSTANCE; } } }