/*
* 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.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;
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;
/**
* 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(), (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.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;
}
}
}