Package org.pentaho.reporting.engine.classic.core

Source Code of org.pentaho.reporting.engine.classic.core.Element

/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2009 Object Refinery Ltd, Pentaho Corporation and Contributors..  All rights reserved.
*/

package org.pentaho.reporting.engine.classic.core;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

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.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.function.ExpressionRuntime;
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.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.style.StyleSheetCarrier;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.engine.classic.core.util.NullOutputStream;
import org.pentaho.reporting.libraries.base.util.Empty;
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. Content elements
* produce printable values when the {@link org.pentaho.reporting.engine.classic.core.Element#getValue(org.pentaho.reporting.engine.classic.core.function.ExpressionRuntime)}
* method is called.
* <p/>
* All elements have a non-null 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 appeareance 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/>
* Use the following code to create and assign a global stylesheet to an element:
* <pre>
* JFreeReport report = .. // created elsewhere
* ElementStyleSheet globalStyle =
*    report.getStyleSheetCollection().createStyleSheet ("a name for the global
* style");
* <p/>
* Element element = .. // created elsewhere
* element.getStyleSheet().addParent(globalStyle);
* report.getItemBand().addElement (element);
* </pre>
* <p/>
* Global stylesheets will always be queried before the parent's stylesheet gets queried. The order of the add-operation
* does matter, StyleSheets which are added last will be preferred over previously added stylesheets.
*
* @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);

  /**
   * A helper class to preserve a recoverable reference to the elements stylesheet.
   */
  private static class InternalElementStyleSheetCarrier
      implements StyleSheetCarrier
  {
    /**
     * An inherited stylesheet of the element.
     */
    private transient ElementStyleSheet styleSheet;
    /**
     * The private stylesheet of the element.
     */
    private InternalElementStyleSheet self;
    /**
     * The stylesheet id of the inherited stylesheet.
     */
    private InstanceID styleSheetID;

    /**
     * Creates a new stylesheet carrier for the given internal stylesheet and the given inherited stylesheet.
     *
     * @param parent     the internal stylesheet
     * @param styleSheet the stylesheet
     */
    protected InternalElementStyleSheetCarrier(final InternalElementStyleSheet parent,
                                               final ElementStyleSheet styleSheet)
    {
      if (parent == null)
      {
        throw new NullPointerException("Internal stylesheet must not be null.");
      }
      if (styleSheet == null)
      {
        throw new NullPointerException("Inherited stylesheet must not be null.");
      }
      this.self = parent;
      this.styleSheet = styleSheet;
      this.styleSheetID = styleSheet.getId();
    }

    /**
     * An internal helper method that gets called to update the reference to the element's internal stylesheet.
     *
     * @param self the new reference to the element's stylesheet
     * @throws NullPointerException if the given self reference is null
     */
    protected void updateParentReference(final InternalElementStyleSheet self)
    {
      if (self == null)
      {
        throw new NullPointerException
            ("Invalid implementation: Self reference cannot be null after cloning.");
      }
      this.self = self;
    }

    /**
     * Clones this reference. During cloning the stylesheet is removed. The stylesheets ID is preserved to allow to
     * recover the stylesheet later.
     *
     * @return the clone.
     * @throws CloneNotSupportedException if cloning failed for some reason.
     */
    public Object clone()
        throws CloneNotSupportedException
    {
      final InternalElementStyleSheetCarrier ic =
          (InternalElementStyleSheetCarrier) super.clone();
      ic.self = null;
      ic.styleSheet = null;
      return ic;
    }

    /**
     * Returns the referenced stylesheet (and recovers the stylesheet if necessary).
     *
     * @return the stylesheet
     * @throws IllegalStateException if the stylesheet could not be recovered.
     */
    public ElementStyleSheet getStyleSheet()
    {
      if (styleSheet != null)
      {
        return styleSheet;
      }
      if (self == null)
      {
        // should not happen in a sane environment ..
        throw new IllegalStateException
            ("Stylesheet was not valid after restore operation.");
      }
      final Element element = self.getElement();
      if (element == null)
      {
        throw new IllegalStateException();
      }
      final ReportDefinition reportDefinition = element.getReportDefinition();
      if (reportDefinition == null)
      {
        // should not happen in a sane environment ..
        throw new IllegalStateException("Stylesheet was not valid after restore operation: " + styleSheetID);
      }
      styleSheet = reportDefinition.getStyleSheetCollection().getStyleSheetByID(styleSheetID);
      return styleSheet;
    }

    /**
     * Invalidates the stylesheet reference. Recovery is started on the next call to <code>getStylesheet()</code>
     */
    public void invalidate()
    {
      this.styleSheet = null;
    }

    /**
     * Checks whether the given stylesheet is the same as the referenced stylesheet in this object.
     *
     * @param style the stylesheet
     * @return true, if both stylesheets share the same instance ID, false otherwise.
     */
    public boolean isSame(final ElementStyleSheet style)
    {
      return style.getId().equals(styleSheetID);
    }
  }

  /**
   * 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;
    /**
     * The parent of the element.
     */
    private Section parent;

    /**
     * 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)
    {
      super("internal-stylesheet:element:" + System.identityHashCode(element));
      this.parent = element.getParentSection();
      this.element = element;
      setGlobalDefaultStyleSheet(element.createGlobalDefaultStyle());
    }

    /**
     * Returns the element for this stylesheet.
     *
     * @return the element.
     */
    public Element getElement()
    {
      return element;
    }

    /**
     * Creates and returns a copy of this object. After the cloning, the new StyleSheet is no longer registered with its
     * parents.
     *
     * @return a clone of this instance.
     * @throws CloneNotSupportedException if cloning the element failed.
     * @see Cloneable
     */
    public Object clone()
        throws CloneNotSupportedException
    {
      final InternalElementStyleSheet es = (InternalElementStyleSheet) super.clone();
      es.parent = null;
      es.element = null;

      final StyleSheetCarrier[] sheets = es.getParentReferences();
      final int length = sheets.length;
      for (int i = 0; i < length; i++)
      {
        final InternalElementStyleSheetCarrier esc = (InternalElementStyleSheetCarrier) sheets[i];
        esc.updateParentReference(es);
      }
      return es;
    }

    /**
     * Creates and returns a copy of this object. After the cloning, the new StyleSheet is no longer registered with its
     * parents.
     *
     * @return a clone of this instance.
     * @throws CloneNotSupportedException if cloning the element failed.
     * @see Cloneable
     */
    public StyleSheet derive()
        throws CloneNotSupportedException
    {
      final InternalElementStyleSheet es = (InternalElementStyleSheet) super.derive();
      es.parent = null;
      es.element = null;

      final StyleSheetCarrier[] sheets = es.getParentReferences();
      final int length = sheets.length;
      for (int i = 0; i < length; i++)
      {
        final InternalElementStyleSheetCarrier esc = (InternalElementStyleSheetCarrier) sheets[i];
        esc.updateParentReference(es);
      }
      return es;
    }

    /**
     * A callback method used by the element to inform that the element's parent changed.
     */
    public void parentChanged()
    {
      if (parent != null)
      {
        setCascadeStyleSheet(null);
      }
      this.parent = element.getParentSection();
      if (parent != null)
      {
        setCascadeStyleSheet(parent.getStyle());
      }
    }

    /**
     * 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;
    }

    /**
     * Creates a stylesheet carrier to reference inherited stylesheets in a secure way.
     *
     * @param styleSheet the stylesheet for which the carrier should be created.
     * @return the stylesheet carrier.
     */
    protected StyleSheetCarrier createCarrier(final ElementStyleSheet styleSheet)
    {
      return new InternalElementStyleSheetCarrier(this, styleSheet);
    }

    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();

  /**
   * The head of the data source chain.
   *
   * @deprecated the data-source should no longer be used. Use one of the element-type declarations instead.
   */
  private DataSource datasource;

//  private ElementType elementType;

  /**
   * 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 styleExpressions;

  private transient ReportAttributeMap attributes;
  private ReportAttributeMap attributeExpressions;
  private transient ReportAttributeMap cachedAttributes;
  private transient long changeTracker;

  /**
   * 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();
    attributes.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE, LegacyType.INSTANCE);
  }

  protected Element(InstanceID id)
  {
    treeLock = id;
    datasource = Element.NULL_DATASOURCE;
    style = new InternalElementStyleSheet(this);
    attributes = new ReportAttributeMap();
    attributes.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE, LegacyType.INSTANCE);
  }

  public void setAttributeExpression(final String namespace, final String name, final Expression value)
  {
    if (attributeExpressions == null)
    {
      attributeExpressions = new ReportAttributeMap();
    }

    final Object oldExpression = this.attributeExpressions.setAttribute(namespace, name, value);
    notifyNodePropertiesChanged(new AttributeExpressionChange(namespace, name, (Expression) oldExpression, value));
  }

  public Expression getAttributeExpression(final String namespace, final String name)
  {
    if (attributeExpressions == null)
    {
      return null;
    }
    return (Expression) 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 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)
  {
    final Object oldValue = attributes.setAttribute(namespace, name, value);
    if (cachedAttributes != null)
    {
      if (cachedAttributes.getChangeTracker() != attributes.getChangeTracker())
      {
        cachedAttributes = null;
      }
    }
    if (notifyChange)
    {
      notifyNodePropertiesChanged(new AttributeChange(namespace, name, oldValue, value));
    }
  }

  public Object getAttribute(final String namespace, final String name)
  {
    return attributes.getAttribute(namespace, name);
  }

  public String[] getAttributeNamespaces()
  {
    return attributes.getNameSpaces();
  }

  public String[] getAttributeNames(final String name)
  {
    return attributes.getNames(name);
  }

  /**
   * 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 getAttributes()
  {
    if (cachedAttributes != null)
    {
      if (cachedAttributes.getChangeTracker() == attributes.getChangeTracker())
      {
        return cachedAttributes;
      }
    }

    cachedAttributes = attributes.createUnmodifiableMap();
    return cachedAttributes;
  }

  public void setElementType(final ElementType elementType)
  {
    if (elementType == null)
    {
      throw new NullPointerException("Element.setElementType(..): ElementType cannot be null");
    }
    attributes.setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE, elementType);
  }

  public ElementType getElementType()
  {
    final Object attribute = attributes.getAttribute
        (AttributeNames.Core.NAMESPACE, AttributeNames.Core.ELEMENT_TYPE);
    if (attribute instanceof ElementType == false)
    {
      return LegacyType.INSTANCE;
    }
    return (ElementType) attribute;
  }

  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.style.parentChanged();
  }

  /**
   * 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.
   */
  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.
   */
  public void setDataSource(final DataSource ds)
  {
    if (ds == null)
    {
      throw new NullPointerException("Element.setDataSource(...) : null data source.");
    }
    this.datasource = ds;
    notifyNodePropertiesChanged();
  }

  /**
   * Queries this Element's datasource for a value.
   *
   * @param runtime the expression runtime for evaluating inline expression.
   * @return the value of the datasource, which can be null.
   * @deprecated this method should not be used directly. Use getElementType().getValue(..) instead.
   */
  public Object getValue(final ExpressionRuntime runtime)
  {
    return getElementType().getValue(runtime, this);
  }

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

  /**
   * 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.
   * @throws CloneNotSupportedException should never happen.
   */
  public Object clone()
      throws CloneNotSupportedException
  {
    final Element e = (Element) super.clone();
    e.style = (InternalElementStyleSheet) style.getCopy();
    e.datasource = (DataSource) datasource.clone();
    e.parent = null;
    e.style.updateElementReference(e);
    e.attributes = (ReportAttributeMap) attributes.clone();

    if (attributeExpressions != null)
    {
      e.attributeExpressions = (ReportAttributeMap) attributeExpressions.clone();
      final String[] namespaces = e.attributeExpressions.getNameSpaces();
      for (int i = 0; i < namespaces.length; i++)
      {
        final String namespace = namespaces[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.clone());
        }
      }
    }

    if (styleExpressions != null)
    {
      e.styleExpressions = (HashMap) styleExpressions.clone();
      final Iterator styleExpressionsIt =
          e.styleExpressions.entrySet().iterator();
      while (styleExpressionsIt.hasNext())
      {
        final Map.Entry entry = (Map.Entry) styleExpressionsIt.next();
        final Expression exp = (Expression) entry.getValue();
        entry.setValue(exp.clone());
      }
    }
    return e;
  }

  /**
   * Creates a deep copy of this element and regenerates all instance-ids.
   *
   * @return the copy of the element.
   * @throws CloneNotSupportedException if there was an error while cloning this or any child object.
   */
  public Element derive() throws CloneNotSupportedException
  {
    final Element e = (Element) super.clone();
    e.treeLock = new InstanceID();
    e.style = (InternalElementStyleSheet) style.derive();
    e.datasource = (DataSource) datasource.clone();
    e.parent = null;
    e.style.updateElementReference(e);
    e.attributes = (ReportAttributeMap) attributes.clone();
    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 = (ReportAttributeMap) 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)
    {
      e.styleExpressions = (HashMap) styleExpressions.clone();
      final Iterator styleExpressionsIt =
          e.styleExpressions.entrySet().iterator();
      while (styleExpressionsIt.hasNext())
      {
        final Map.Entry entry = (Map.Entry) styleExpressionsIt.next();
        final Expression exp = (Expression) entry.getValue();
        entry.setValue(exp.getInstance());
      }
    }


    return e;

  }

  /**
   * 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.
   *
   * @return 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;
  }

  /**
   * 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.
   */
  protected ElementDefaultStyleSheet createGlobalDefaultStyle()
  {
    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();
    }
    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 (Expression) 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 getStyleExpressions()
  {
    if (styleExpressions != null)
    {
      return Collections.unmodifiableMap(styleExpressions);
    }
    return Empty.MAP;
  }

  /**
   * 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;
  }

  /**
   * 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 && (data.isTransient() == false))
        {
          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);
                // todo: Convert into a byte-array key;
                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
        {
          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 reportDefinition = getReportDefinition();
    if (reportDefinition instanceof AbstractReportDefinition == false)
    {
      final ResourceManager retval = new ResourceManager();
      retval.registerDefaults();
      return retval;
    }

    final AbstractReportDefinition abstractReportDefinition = (AbstractReportDefinition) reportDefinition;
    return abstractReportDefinition.getResourceManager();
  }

  /**
   * 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(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 StringBuffer b = new StringBuffer(100);
    b.append(this.getClass().getName());
    b.append("={name=\"");
    b.append(getName());
    b.append("\", type=\"");
    b.append(getElementTypeName());
    b.append("\"}");
    return b.toString();
  }


}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.Element

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.