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

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

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

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

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import javax.swing.event.EventListenerList;

import org.pentaho.reporting.engine.classic.core.designtime.Change;
import org.pentaho.reporting.engine.classic.core.designtime.StyleChange;
import org.pentaho.reporting.engine.classic.core.event.ReportModelEvent;
import org.pentaho.reporting.engine.classic.core.event.ReportModelListener;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.engine.classic.core.function.ExpressionCollection;
import org.pentaho.reporting.engine.classic.core.function.StructureFunction;
import org.pentaho.reporting.engine.classic.core.style.StyleSheetCollection;
import org.pentaho.reporting.engine.classic.core.util.IntegerCache;
import org.pentaho.reporting.engine.classic.core.util.ReportProperties;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.engine.classic.core.wizard.DataSchemaDefinition;
import org.pentaho.reporting.engine.classic.core.wizard.DefaultDataSchemaDefinition;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceManager;

/**
* The AbstractReportDefinition serves as base-implementation for both the SubReport and the global JFreeReport
* instance. There's no point to subclass this class any further.
* <p/>
* ReportDefinitions define the query string to "default" by default, change this to reflect the accepted queries in
* your data-source.
*
* @author Thomas Morgner
*/
public abstract class AbstractReportDefinition extends Section
    implements ReportDefinition
{
  /**
   * Storage for arbitrary properties that a user can assign to the report.
   */
  private ReportProperties properties;
  /**
   * The resource bundle factory is used when generating localized reports.
   */
  private ResourceBundleFactory resourceBundleFactory;
  /**
   * Storage for the expressions in the report.
   */
  private ExpressionCollection expressions;
  /**
   * An ordered list of report groups (each group defines its own header and footer).
   */
  private Group rootGroup;
  /**
   * The report header band (printed once at the start of the report).
   */
  private ReportHeader reportHeader;
  /**
   * The report footer band (printed once at the end of the report).
   */
  private ReportFooter reportFooter;
  /**
   * The page header band (printed at the start of every page).
   */
  private PageHeader pageHeader;
  /**
   * The page footer band (printed at the end of every page).
   */
  private PageFooter pageFooter;
  /**
   * The watermark band.
   */
  private Watermark watermark;

  /**
   * The stylesheet collection used for this report.
   */
  private StyleSheetCollection styleSheetCollection;

  private transient EventListenerList eventListeners;
  private long nonVisualsChangeTracker;
  private long datasourceChangeTracker;
 
  private DataSchemaDefinition dataSchemaDefinition;

  protected AbstractReportDefinition(final InstanceID id)
  {
    super(id);
    init();
  }
  /**
   * Creates a new instance. This initializes all properties to their defaults - especially for subreports you have to
   * set sensible values before you can use them later.
   */
  protected AbstractReportDefinition()
  {
    init();
  }

  private void init()
  {
    this.dataSchemaDefinition = new DefaultDataSchemaDefinition();
    this.properties = new ReportProperties();
    this.styleSheetCollection = new StyleSheetCollection();
    this.rootGroup = new RelationalGroup();
    this.reportHeader = new ReportHeader();
    this.reportFooter = new ReportFooter();
    this.pageHeader = new PageHeader();
    this.pageFooter = new PageFooter();
    this.watermark = new Watermark();

    this.expressions = new ExpressionCollection();
    registerAsChild(rootGroup);
    registerAsChild(reportHeader);
    registerAsChild(reportFooter);
    registerAsChild(pageHeader);
    registerAsChild(pageFooter);
    registerAsChild(watermark);

    addReportModelListener(new ResourceBundleChangeHandler());
  }


  /**
   * Returns the resource bundle factory for this report definition. The {@link ResourceBundleFactory} is used in
   * internationalized reports to create the resourcebundles holding the localized resources.
   *
   * @return the assigned resource bundle factory.
   */
  public ResourceBundleFactory getResourceBundleFactory()
  {
    return resourceBundleFactory;
  }

  /**
   * Redefines the resource bundle factory for the report.
   *
   * @param resourceBundleFactory the new resource bundle factory, never null.
   * @throws NullPointerException if the given ResourceBundleFactory is null.
   */
  public void setResourceBundleFactory(final ResourceBundleFactory resourceBundleFactory)
  {
    this.resourceBundleFactory = resourceBundleFactory;
    this.notifyNodePropertiesChanged();
  }

  public int getPreProcessorCount()
  {
    final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS);
    if (maybeArray instanceof ReportPreProcessor[])
    {
      final ReportPreProcessor[] preprocessors = (ReportPreProcessor[]) maybeArray;
      return preprocessors.length;
    }
    return 0;
  }

  public ReportPreProcessor[] getPreProcessors()
  {
    final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS);
    if (maybeArray instanceof ReportPreProcessor[])
    {
      final ReportPreProcessor[] preprocessors = (ReportPreProcessor[]) maybeArray;
      return (ReportPreProcessor[]) preprocessors.clone();
    }
    return new ReportPreProcessor[0];
  }

  public ReportPreProcessor getPreProcessor(final int index)
  {
    final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS);
    if (maybeArray instanceof ReportPreProcessor[])
    {
      final ReportPreProcessor[] preprocessors = (ReportPreProcessor[]) maybeArray;
      return preprocessors[index];
    }
    throw new IndexOutOfBoundsException();
  }

  public void addPreProcessor(final ReportPreProcessor preProcessor)
  {
    if (preProcessor == null)
    {
      throw new NullPointerException();
    }

    final ReportPreProcessor[] preprocessors = getPreProcessors();
    final ArrayList newProcessors = new ArrayList(Math.max(10, preprocessors.length));
    for (int i = 0; i < preprocessors.length; i++)
    {
      final ReportPreProcessor preprocessor = preprocessors[i];
      newProcessors.add(preprocessor);
    }
    newProcessors.add(preProcessor);

    final ReportPreProcessor[] newArray =
        (ReportPreProcessor[]) newProcessors.toArray(new ReportPreProcessor[newProcessors.size()]);
    setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, newArray);
  }

  public void removePreProcessor(final ReportPreProcessor preProcessor)
  {
    if (preProcessor == null)
    {
      throw new NullPointerException();
    }

    final ReportPreProcessor[] preprocessors = getPreProcessors();
    final ArrayList newProcessors = new ArrayList(Math.max(10, preprocessors.length));
    boolean found = false;
    for (int i = 0; i < preprocessors.length; i++)
    {
      final ReportPreProcessor preprocessor = preprocessors[i];
      if (found || preprocessor != preProcessor)
      {
        newProcessors.add(preprocessor);
        found = true;
      }
    }
    if (found)
    {
      final ReportPreProcessor[] newArray =
          (ReportPreProcessor[]) newProcessors.toArray(new ReportPreProcessor[newProcessors.size()]);
      setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, newArray);
    }
  }

  public Group getRootGroup()
  {
    return rootGroup;
  }

  public void setRootGroup(final Group rootGroup)
  {
    if (rootGroup == null)
    {
      throw new NullPointerException();
    }
    if (rootGroup instanceof CrosstabGroup == false &&
        rootGroup instanceof RelationalGroup == false)
    {
      throw new IllegalArgumentException("Only Crosstabs or relational-groups are permitted at the root");
    }
    validateLooping(rootGroup);
    if (unregisterParent(rootGroup))
    {
      return;
    }

    final Element oldElement = this.rootGroup;
    this.rootGroup.setParent(null);
    this.rootGroup = rootGroup;
    this.rootGroup.setParent(this);
    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(rootGroup);
  }

  /**
   * Adds a property to the report.
   * <p/>
   * If a property with the given name already exists, the property will be updated with the new value. If the supplied
   * value is <code>null</code>, the property will be removed.
   * <p/>
   * Developers are free to add any properties they want to a report, and then display those properties in the report.
   * For example, you might add a 'user.name' property, so that you can display the username in the header of a report.
   *
   * @param key   the key.
   * @param value the value.
   * @deprecated Properties should not be used. Use the master-report's parameters instead.
   */
  public void setProperty(final String key, final Object value)
  {
    if (key == null)
    {
      throw new NullPointerException();
    }
    this.properties.put(key, value);
    this.notifyNodePropertiesChanged();
  }

  /**
   * Returns the report properties collection for this report.
   * <p/>
   * These properties are inherited to all ReportStates generated for this report.
   *
   * @return the report properties.
   * @deprecated Report-Properties should not be used anymore.
   */
  public ReportProperties getProperties()
  {
    return properties;
  }

  /**
   * Returns the value of the property with the specified key.
   *
   * @param key the key.
   * @return the property value.
   * @deprecated Do not use this method anymore. Use the master report's parameters instead.
   */
  public Object getProperty(final String key)
  {
    if (key == null)
    {
      throw new NullPointerException();
    }
    return this.properties.get(key);
  }

  /**
   * Sets the report header.
   *
   * @param header the report header (<code>null</code> not permitted).
   */
  public void setReportHeader(final ReportHeader header)
  {
    if (header == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setReportHeader(...) : null not permitted.");
    }
    validateLooping(header);
    if (unregisterParent(header))
    {
      return;
    }

    final Element oldElement = this.reportHeader;
    this.reportHeader.setParent(null);
    this.reportHeader = header;
    this.reportHeader.setParent(this);
    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(header);
  }

  /**
   * Returns the report header.
   *
   * @return the report header (never <code>null</code>).
   */
  public ReportHeader getReportHeader()
  {
    return reportHeader;
  }

  /**
   * Sets the report footer.
   *
   * @param footer the report footer (<code>null</code> not permitted).
   */
  public void setReportFooter(final ReportFooter footer)
  {
    if (footer == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setReportFooter(...) : null not permitted.");
    }
    validateLooping(footer);
    if (unregisterParent(footer))
    {
      return;
    }
    final Element oldElement = this.reportFooter;
    this.reportFooter.setParent(null);
    this.reportFooter = footer;
    this.reportFooter.setParent(this);
    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(footer);
  }

  /**
   * Returns the page footer.
   *
   * @return the report footer (never <code>null</code>).
   */
  public ReportFooter getReportFooter()
  {
    return reportFooter;
  }

  /**
   * Sets the page header.
   *
   * @param header the page header (<code>null</code> not permitted).
   */
  public void setPageHeader(final PageHeader header)
  {
    if (header == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setPageHeader(...) : null not permitted.");
    }

    validateLooping(header);
    if (unregisterParent(header))
    {
      return;
    }
    final Element oldElement = this.pageHeader;
    this.pageHeader.setParent(null);
    this.pageHeader = header;
    this.pageHeader.setParent(this);
    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(header);
  }

  /**
   * Returns the page header.
   *
   * @return the page header (never <code>null</code>).
   */
  public PageHeader getPageHeader()
  {
    return pageHeader;
  }

  /**
   * Sets the page footer.
   *
   * @param footer the page footer (<code>null</code> not permitted).
   */
  public void setPageFooter(final PageFooter footer)
  {
    if (footer == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setPageFooter(...) : null not permitted.");
    }
    validateLooping(footer);
    if (unregisterParent(footer))
    {
      return;
    }
    final Element oldElement = this.pageFooter;
    this.pageFooter.setParent(null);
    this.pageFooter = footer;
    this.pageFooter.setParent(this);
    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(footer);
  }

  /**
   * Returns the page footer.
   *
   * @return the page footer (never <code>null</code>).
   */
  public PageFooter getPageFooter()
  {
    return pageFooter;
  }

  /**
   * Sets the watermark band for the report.
   *
   * @param band the new watermark band (<code>null</code> not permitted).
   */
  public void setWatermark(final Watermark band)
  {
    if (band == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setWatermark(...) : null not permitted.");
    }

    validateLooping(band);
    if (unregisterParent(band))
    {
      return;
    }

    final Element oldElement = this.watermark;
    this.watermark.setParent(null);
    this.watermark = band;
    this.watermark.setParent(this);
    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(band);
  }

  /**
   * Returns the report's watermark band.
   *
   * @return the watermark band (never <code>null</code>).
   */
  public NoDataBand getNoDataBand()
  {
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    return dataBody.getNoDataBand();
  }

  /**
   * Sets the watermark band for the report.
   *
   * @param band the new watermark band (<code>null</code> not permitted).
   */
  public void setNoDataBand(final NoDataBand band)
  {
    if (band == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setNoDataBand(...) : null not permitted.");
    }
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    dataBody.setNoDataBand(band);
  }

  /**
   * Returns the report's watermark band.
   *
   * @return the watermark band (never <code>null</code>).
   */
  public Watermark getWatermark()
  {
    return this.watermark;
  }

  /**
   * Sets the item band for the report.
   *
   * @param band the new item band (<code>null</code> not permitted).
   */
  public void setItemBand(final ItemBand band)
  {
    if (band == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setItemBand(...) : null not permitted.");
    }
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    dataBody.setItemBand(band);
  }

  /**
   * Returns the report's item band.
   *
   * @return the item band (never <code>null</code>).
   */
  public ItemBand getItemBand()
  {
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    return dataBody.getItemBand();
  }


  /**
   * Sets the item band for the report.
   *
   * @param band the new item band (<code>null</code> not permitted).
   */
  public void setDetailsHeader(final DetailsHeader band)
  {
    if (band == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setDetailsHeader(...) : null not permitted.");
    }
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    dataBody.setDetailsHeader(band);
  }

  /**
   * Returns the details header band.
   *
   * @return The details header band.
   */
  public DetailsHeader getDetailsHeader()
  {
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    return dataBody.getDetailsHeader();
  }

  /**
   * Sets the item band for the report.
   *
   * @param band the new item band (<code>null</code> not permitted).
   */
  public void setDetailsFooter(final DetailsFooter band)
  {
    if (band == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setDetailsFooter(...) : null not permitted.");
    }
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    dataBody.setDetailsFooter(band);
  }

  /**
   * Returns the details header  band.
   *
   * @return The details header  band.
   */
  public DetailsFooter getDetailsFooter()
  {
    final Group group = getInnerMostGroup();
    final GroupDataBody dataBody = (GroupDataBody) group.getBody();
    return dataBody.getDetailsFooter();
  }

  private Group getInnerMostGroup()
  {
    Group existingGroup = rootGroup;
    GroupBody gb = existingGroup.getBody();
    while (gb != null)
    {
      final int count = gb.getElementCount();
      GroupBody locatedBody = null;
      for (int i = 0; i < count; i++)
      {
        final ReportElement element = gb.getElement(i);
        if (element instanceof Group)
        {
          existingGroup = (Group) element;
          locatedBody = existingGroup.getBody();
          break;
        }
      }
      if (locatedBody == null)
      {
        gb = null;
      }
      else
      {
        gb = locatedBody;
      }
    }
    return existingGroup;
  }

  private Group getInnerMostRelationalGroup()
  {
    Group existingGroup = rootGroup;
    GroupBody gb = existingGroup.getBody();
    while (gb != null)
    {
      final int count = gb.getElementCount();
      boolean found = false;
      for (int i = 0; i < count; i++)
      {
        final ReportElement element = gb.getElement(i);
        if (element instanceof RelationalGroup)
        {
          existingGroup = (Group) element;
          gb = existingGroup.getBody();
          found = true;
          break;
        }
      }
      if (found == false)
      {
        gb = null;
      }
    }
    return existingGroup;
  }

  /**
   * Adds a group to the report. This replaces the group body on the group with a new data-group-body composed of the
   * existing itemband and no-databand.
   *
   * @param group the group.
   */
  public void addGroup(final RelationalGroup group)
  {
    if (group == null)
    {
      throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
    }

    final Group existingGroup = getInnerMostRelationalGroup();
    final GroupBody gb = existingGroup.getBody();
    existingGroup.setBody(new SubGroupBody(group));
    group.setBody(gb);
  }

  /**
   * Adds a crosstab group. This replaces any existing crosstabs and all the details sections.
   *
   * @param group
   */
  public void addGroup(final CrosstabGroup group)
  {
    if (group == null)
    {
      throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
    }

    final Group existingGroup = getInnerMostRelationalGroup();
    existingGroup.setBody(new SubGroupBody(group));
  }


  public void removeGroup(final CrosstabGroup group)
  {
    if (group == null)
    {
      throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
    }

    if (rootGroup == group)
    {
      removeRootGroup();
      return;
    }


    final Group existingGroup = getInnerMostRelationalGroup();
    final GroupBody gb = existingGroup.getBody();
    if (gb instanceof SubGroupBody)
    {
      final SubGroupBody sgb = (SubGroupBody) gb;
      if (sgb.getGroup() == group)
      {
        existingGroup.setBody(new GroupDataBody());
      }
    }
  }

  public void removeGroup(final RelationalGroup deleteGroup)
  {
    // Checks if we have a group to remove if not then throw an exception
    if (deleteGroup == null)
    {
      throw new NullPointerException("AbstractReporDefinition.addGroup(..) : Null not permitted");
    }

    // Special case check to see if we're removing the root group.
    if (rootGroup == deleteGroup) // If we're at root then
    {
      removeRootGroup()// Remove it an exit
      return;
    }

    // Walk through the groups and find the one that we need to remove
    Group currentGroup = rootGroup;
    Group parentGroup = null;
    GroupBody currentGroupBody = currentGroup.getBody();
    SubGroupBody sgb = null;
    while (currentGroupBody instanceof SubGroupBody && currentGroup != deleteGroup)
    {
      parentGroup = currentGroup;
      sgb = (SubGroupBody) currentGroupBody;
      currentGroup = sgb.getGroup();
      currentGroupBody = currentGroup.getBody();
    }

    if (currentGroup == deleteGroup) // if this is true then we found the group we need to remove
    {
      parentGroup.setBody(currentGroupBody);
      final SubGroupBody subGroupBody = (SubGroupBody) currentGroup.getParentSection();
      subGroupBody.setParent(parentGroup);
    }
  }

  private void removeRootGroup()
  {
    final Group group = rootGroup;
    final GroupBody rootBody = rootGroup.getBody();
    if (group instanceof CrosstabGroup)
    {
      rootGroup = new CrosstabGroup();
      rootGroup.setBody(rootBody);
    }
    else
    {
      if (rootBody instanceof SubGroupBody)
      {
        final SubGroupBody newRootGroup = (SubGroupBody) rootBody;
        rootGroup.removeElement(rootBody);
        rootGroup = newRootGroup.getGroup();
        registerAsChild(rootGroup);
      }
      else
      {
        rootGroup = new RelationalGroup();
        rootGroup.setBody(rootBody);
      }
    }
    unregisterAsChild(group);
    registerAsChild(rootGroup);
    notifyNodeChildRemoved(group);
    notifyNodeChildAdded(rootGroup);
  }

  /**
   * Sets the groups for this report. If no list (null) or an empty list is given, an default group is created. This
   * default group contains no elements and starts at the first record of the data and ends on the last record.
   *
   * @param groupList the list of groups.
   */
  public void setGroups(final GroupList groupList)
  {
    if (groupList == null)
    {
      throw new NullPointerException("GroupList must not be null");
    }

    final ItemBand ib = getItemBand();
    final NoDataBand nd = getNoDataBand();
    final DetailsHeader detailsHeader = getDetailsHeader();
    final DetailsFooter detailsFooter = getDetailsFooter();

    final Group newRootGroup = groupList.constructRootGroup();
    if (this.rootGroup == newRootGroup)
    {
      return;
    }

    validateLooping(newRootGroup);
    if (unregisterParent(newRootGroup))
    {
      return;
    }
    final Element oldElement = this.rootGroup;
    this.rootGroup.setParent(null);
    this.rootGroup = newRootGroup;
    this.rootGroup.setParent(this);

    notifyNodeChildRemoved(oldElement);
    notifyNodeChildAdded(rootGroup);

    setDetailsFooter(detailsFooter);
    setDetailsHeader(detailsHeader);
    setItemBand(ib);
    setNoDataBand(nd);

  }

  /**
   * Returns the number of groups in this report. <P> Every report has at least one group defined.
   *
   * @return the group count.
   */
  public int getGroupCount()
  {
    int result = 1; // we always have at least a default-group.

    Group existingGroup = rootGroup;
    GroupBody gb = existingGroup.getBody();
    while (gb != null)
    {
      final int count = gb.getElementCount();
      boolean found = false;
      for (int i = 0; i < count; i++)
      {
        final ReportElement element = gb.getElement(i);
        if (element instanceof Group)
        {
          existingGroup = (Group) element;
          result += 1;
          gb = existingGroup.getBody();
          found = true;
          break;
        }
      }
      if (found == false)
      {
        gb = null;
      }
    }

    return result;
  }

  /**
   * Returns the group at the specified index or null, if there is no such group.
   *
   * @param groupIndex the group index.
   * @return the requested group.
   * @throws IllegalArgumentException  if the count is negative.
   * @throws IndexOutOfBoundsException if the count is greater than the number of defined groups.
   */
  public Group getGroup(final int groupIndex)
  {
    if (groupIndex < 0)
    {
      throw new IllegalArgumentException("GroupCount must not be negative");
    }
    if (groupIndex == 0)
    {
      return rootGroup;
    }

    int result = 0; // we always have at least a default-group.

    Group existingGroup = rootGroup;
    GroupBody gb = existingGroup.getBody();
    while (gb != null)
    {
      final int count = gb.getElementCount();
      boolean found = false;
      for (int i = 0; i < count; i++)
      {
        final ReportElement element = gb.getElement(i);
        if (element instanceof Group)
        {
          existingGroup = (Group) element;
          result += 1;
          if (result == groupIndex)
          {
            return existingGroup;
          }
          gb = existingGroup.getBody();
          found = true;
          break;
        }
      }
      if (found == false)
      {
        gb = null;
      }
    }
    throw new IndexOutOfBoundsException("No group defined at the given index. Max-index=" + result);
  }

  /**
   * Searches a group by its defined name. This method returns null, if the group was not found.
   *
   * @param name the name of the group.
   * @return the group or null if not found.
   */
  public RelationalGroup getGroupByName(final String name)
  {
    if (name == null)
    {
      throw new NullPointerException("AbstractReporDefinition.getGroupByName(..) : Null not permitted");
    }

    if (rootGroup instanceof RelationalGroup == false)
    {
      return null;
    }

    if (rootGroup.getName().equals(name))
    {
      return (RelationalGroup) rootGroup;
    }
    GroupBody gb = rootGroup.getBody();
    while (gb instanceof SubGroupBody)
    {
      final SubGroupBody sgb = (SubGroupBody) gb;
      final Group group = sgb.getGroup();
      if (group instanceof RelationalGroup == false)
      {
        return null;
      }
      if (name.equals(group.getName()))
      {
        return (RelationalGroup) group;
      }
      gb = group.getBody();
    }
    return null;
  }

  /**
   * Adds a function to the report's collection of expressions.
   *
   * @param function the function.
   */
  public void addExpression(final Expression function)
  {
    if (function == null)
    {
      throw new NullPointerException("AbstractReporDefinition.addExpression(..) : Null not permitted");
    }

    expressions.add(function);
    notifyNodeChildAdded(function);
  }

  public int getQueryTimeout()
  {
    final Object queryTimeoutText =
        getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY_TIMEOUT);
    if (queryTimeoutText instanceof Number)
    {
      final Number n = (Number) queryTimeoutText;
      return n.intValue();
    }
    return 0;
  }

  public void setQueryTimeout(final int queryTimeout)
  {
    setAttribute(AttributeNames.Internal.NAMESPACE,
        AttributeNames.Internal.QUERY_TIMEOUT, IntegerCache.getInteger(queryTimeout));
  }

  public int getQueryLimit()
  {
    final Object queryLimitText =
        getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY_LIMIT);
    if (queryLimitText instanceof Number)
    {
      final Number n = (Number) queryLimitText;
      return n.intValue();
    }
    return -1;
  }

  public void setQueryLimit(final int queryLimit)
  {
    setAttribute(AttributeNames.Internal.NAMESPACE,
        AttributeNames.Internal.QUERY_LIMIT, IntegerCache.getInteger(queryLimit));
  }

  /**
   * Returns a new query or query-name that is used when retrieving the data from the data-factory.
   *
   * @return the query-string.
   */
  public String getQuery()
  {
    return (String) getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY);
  }

  /**
   * Defines a new query or query-name that is used when retrieving the data from the data-factory.
   *
   * @param query the query-string.
   * @see DataFactory#queryData(String,DataRow)
   */
  public void setQuery(final String query)
  {
    setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.QUERY, query);
  }

  /**
   * Returns the stylesheet collection of this report. The stylesheet collection is fixed for the report and all
   * elements of the report. When a band or group is added to the report it will get registered with this stylesheet
   * collection and cannot be used in an different report.
   *
   * @return the stylesheet collection of the report, never null.
   */
  public StyleSheetCollection getStyleSheetCollection()
  {
    return styleSheetCollection;
  }

  /**
   * Returns the current datarow assigned to this report definition. JFreeReport objects do not hold a working DataRow,
   * as the final contents of the data cannot be known, until the reporting has started.
   *
   * @return the default implementation for non-processed reports.
   */
  public DataRow getDataRow()
  {
    return new ParameterDataRow(properties);
  }

  /**
   * Returns the expressions for the report. When adding or removing expressions on the list, make sure to call
   * "notifyStructureChanged" afterwards when in design-mode.
   *
   * @return the expressions.
   */
  public ExpressionCollection getExpressions()
  {
    return expressions;
  }

  /**
   * Sets the expressions for the report.
   *
   * @param expressions the expressions (<code>null</code> not permitted).
   */
  public void setExpressions(final ExpressionCollection expressions)
  {
    if (expressions == null)
    {
      throw new NullPointerException("AbstractReportDefinition.setExpressions(...) : null not permitted.");
    }
    this.expressions = expressions;
    notifyNodeStructureChanged();
  }

  /**
   * Clones the report.
   *
   * @return the clone.
   * @throws CloneNotSupportedException this should never happen.
   */
  public Object clone()
      throws CloneNotSupportedException
  {
    final AbstractReportDefinition report = (AbstractReportDefinition) super.clone();
    report.eventListeners = null;
    report.rootGroup = (Group) rootGroup.clone();
    report.watermark = (Watermark) watermark.clone();
    report.pageFooter = (PageFooter) pageFooter.clone();
    report.pageHeader = (PageHeader) pageHeader.clone();
    report.reportFooter = (ReportFooter) reportFooter.clone();
    report.reportHeader = (ReportHeader) reportHeader.clone();
    report.properties = (ReportProperties) properties.clone();
    report.expressions = (ExpressionCollection) expressions.clone();
    report.styleSheetCollection = (StyleSheetCollection) styleSheetCollection.clone();
    report.dataSchemaDefinition = (DataSchemaDefinition) dataSchemaDefinition.clone();
    report.rootGroup.setParent(report);
    report.reportHeader.setParent(report);
    report.reportFooter.setParent(report);
    report.pageHeader.setParent(report);
    report.pageFooter.setParent(report);
    report.watermark.setParent(report);

    final ReportPreProcessor[] reportPreProcessors = report.getPreProcessors();
    for (int i = 0; i < reportPreProcessors.length; i++)
    {
      reportPreProcessors[i] = (ReportPreProcessor) reportPreProcessors[i].clone();
    }
    report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, reportPreProcessors);

    final StructureFunction[] structureFunctions = report.getStructureFunctions();
    for (int i = 0; i < structureFunctions.length; i++)
    {
      structureFunctions[i] = (StructureFunction) structureFunctions[i].clone();
    }
    report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, structureFunctions);
    report.addReportModelListener(new ResourceBundleChangeHandler());
    return report;
  }

  public Element derive()
      throws CloneNotSupportedException
  {
    final AbstractReportDefinition report = (AbstractReportDefinition) super.derive();
    report.eventListeners = null;
    report.rootGroup = (Group) rootGroup.derive();
    report.watermark = (Watermark) watermark.derive();
    report.pageFooter = (PageFooter) pageFooter.derive();
    report.pageHeader = (PageHeader) pageHeader.derive();
    report.reportFooter = (ReportFooter) reportFooter.derive();
    report.reportHeader = (ReportHeader) reportHeader.derive();
    report.properties = (ReportProperties) properties.clone();
    report.expressions = (ExpressionCollection) expressions.clone();
    report.styleSheetCollection = (StyleSheetCollection) styleSheetCollection.clone();
    report.dataSchemaDefinition = (DataSchemaDefinition) dataSchemaDefinition.clone();
    report.rootGroup.setParent(report);
    report.reportHeader.setParent(report);
    report.reportFooter.setParent(report);
    report.pageHeader.setParent(report);
    report.pageFooter.setParent(report);
    report.watermark.setParent(report);

    final ReportPreProcessor[] reportPreProcessors = report.getPreProcessors();
    for (int i = 0; i < reportPreProcessors.length; i++)
    {
      reportPreProcessors[i] = (ReportPreProcessor) reportPreProcessors[i].clone();
    }
    report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.PREPROCESSORS, reportPreProcessors);

    final StructureFunction[] structureFunctions = report.getStructureFunctions();
    for (int i = 0; i < structureFunctions.length; i++)
    {
      structureFunctions[i] = (StructureFunction) structureFunctions[i].getInstance();
    }
    report.setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, structureFunctions);
    report.addReportModelListener(new ResourceBundleChangeHandler());
    return report;
  }

  public void setElementAt(final int position, final Element element)
  {
    switch (position)
    {
      case 0:
        setPageHeader((PageHeader) element);
        break;
      case 1:
        setReportHeader((ReportHeader) element);
        break;
      case 2:
        setRootGroup((Group) element);
        break;
      case 3:
        setReportFooter((ReportFooter) element);
        break;
      case 4:
        setPageFooter((PageFooter) element);
        break;
      case 5:
        setWatermark((Watermark) element);
        break;
      default:
        throw new IndexOutOfBoundsException();

    }
  }

  protected void removeElement(final Element element)
  {
    if (element == null)
    {
      throw new NullPointerException("AbstractReporDefinition.removeElement(..) : Null not permitted");
    }

    if (pageHeader == element)
    {
      this.pageHeader.setParent(null);
      this.pageHeader = new PageHeader();
      this.pageHeader.setParent(this);

      notifyNodeChildRemoved(element);
      notifyNodeChildAdded(this.pageHeader);
    }
    else if (watermark == element)
    {
      this.watermark.setParent(null);
      this.watermark = new Watermark();
      this.watermark.setParent(this);

      notifyNodeChildRemoved(element);
      notifyNodeChildAdded(this.watermark);
    }
    else if (reportHeader == element)
    {
      this.reportHeader.setParent(null);
      this.reportHeader = new ReportHeader();
      this.reportHeader.setParent(this);

      notifyNodeChildRemoved(element);
      notifyNodeChildAdded(this.reportHeader);
    }
    else if (rootGroup == element)
    {
      removeRootGroup();
    }
    else if (reportFooter == element)
    {
      this.reportFooter.setParent(null);
      this.reportFooter = new ReportFooter();
      this.reportFooter.setParent(this);

      notifyNodeChildRemoved(element);
      notifyNodeChildAdded(this.reportFooter);
    }
    else if (pageFooter == element)
    {
      this.pageFooter.setParent(null);
      this.pageFooter = new PageFooter();
      this.pageFooter.setParent(this);

      notifyNodeChildRemoved(element);
      notifyNodeChildAdded(this.pageFooter);
    }
  }

  public int getElementCount()
  {
    return 6;
  }

  public ReportElement getElement(final int index)
  {
    switch (index)
    {
      case 0:
        return pageHeader;
      case 1:
        return reportHeader;
      case 2:
        return rootGroup;
      case 3:
        return reportFooter;
      case 4:
        return pageFooter;
      case 5:
        return watermark;
      default:
        throw new IndexOutOfBoundsException();

    }
  }

  /**
   * Defines the content base for the report. The content base will be used to resolve relative URLs during the report
   * generation and resource loading. If there is no content base defined, it will be impossible to resolve relative
   * paths.
   *
   * @param key the content base or null.
   */
  public void setContentBase(final ResourceKey key)
  {
    setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.CONTENT_BASE, key);
  }

  /**
   * Returns the content base of this report. The content base is used to resolve relative URLs during the report
   * generation and resource loading. If there is no content base defined, it will be impossible to resolve relative
   * paths.
   *
   * @return the content base or null, if no content base is defined.
   */
  public ResourceKey getContentBase()
  {
    final Object attribute = getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.CONTENT_BASE);
    if (attribute instanceof ResourceKey)
    {
      return (ResourceKey) attribute;
    }
    return getDefinitionSource();
  }

  public void setDefinitionSource(final ResourceKey key)
  {
    setAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.SOURCE, key);
  }

  public ResourceKey getDefinitionSource()
  {
    final Object attribute = getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.SOURCE);
    if (attribute instanceof ResourceKey)
    {
      return (ResourceKey) attribute;
    }
    return null;
  }

  /**
   * Returns the currently assigned report definition.
   *
   * @return the report definition or null, if no report has been assigned.
   */
  public ReportDefinition getReportDefinition()
  {
    return this;
  }

  /**
   * Returns the data factory that has been assigned to this report. The data factory will never be null.
   *
   * @return the data factory.
   */
  public abstract DataFactory getDataFactory();

  /**
   * Sets the data factory for the report.
   *
   * @param dataFactory the data factory for the report, never null.
   */
  public abstract void setDataFactory(final DataFactory dataFactory);


  public void addReportModelListener(final ReportModelListener listener)
  {
    if (eventListeners == null)
    {
      eventListeners = new EventListenerList();
    }
    this.eventListeners.add(ReportModelListener.class, listener);
  }

  public void removeReportModelListener(final ReportModelListener listener)
  {
    if (eventListeners == null)
    {
      return;
    }
    eventListeners.remove(ReportModelListener.class, listener);
  }

  public void fireModelLayoutChanged(final ReportElement node, final int type, final Object parameter)
  {
    if (node == this)
    {
      if (parameter instanceof Change == false)
      {
        nonVisualsChangeTracker += 1;
      }
      if (parameter instanceof DataFactory)
      {
        datasourceChangeTracker += 1;
      }
    }
   
    updateInternalChangeFlag();

    if (eventListeners != null)
    {
      final ReportModelEvent event = new ReportModelEvent(this, node, type, parameter);
      final ReportModelListener[] listeners = (ReportModelListener[])
          eventListeners.getListeners(ReportModelListener.class);
      for (int i = 0; i < listeners.length; i++)
      {
        final ReportModelListener listener = listeners[i];
        listener.nodeChanged(event);
      }
    }
  }

  public long getDatasourceChangeTracker()
  {
    return datasourceChangeTracker;
  }

  public long getNonVisualsChangeTracker()
  {
    return nonVisualsChangeTracker;
  }

  public void removeExpression(final Expression expression)
  {
    expressions.removeExpression(expression);
    notifyNodeChildRemoved(expression);
  }


  public DataSchemaDefinition getDataSchemaDefinition()
  {
    return dataSchemaDefinition;
  }

  public void setDataSchemaDefinition(final DataSchemaDefinition dataSchemaDefinition)
  {
    if (dataSchemaDefinition == null)
    {
      throw new NullPointerException();
    }
    this.dataSchemaDefinition = dataSchemaDefinition;
    notifyNodePropertiesChanged();
  }

  public abstract ResourceManager getResourceManager();


  /**
   * Adds a structural function to the report. Structural functions perform content preparation and maintainance
   * operations before elements are layouted or printed.
   * <p/>
   * Structural function can live on their own processing level and are evaluated after the user expressions but before
   * the layout expressions have been evaluated.
   *
   * @param function the structure function.
   */
  public void addStructureFunction(final StructureFunction function)
  {
    if (function == null)
    {
      throw new NullPointerException();
    }

    final StructureFunction[] structureFunctions = getStructureFunctions();
    final ArrayList newProcessors = new ArrayList(Math.max(10, structureFunctions.length));
    for (int i = 0; i < structureFunctions.length; i++)
    {
      final StructureFunction structureFunction = structureFunctions[i];
      newProcessors.add(structureFunction);
    }
    newProcessors.add(function);

    final StructureFunction[] newArray =
        (StructureFunction[]) newProcessors.toArray(new StructureFunction[newProcessors.size()]);
    setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, newArray);
  }

  /**
   * Returns the number of structural functions added to the report.
   *
   * @return the function count.
   */
  public int getStructureFunctionCount()
  {
    final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS);
    if (maybeArray instanceof StructureFunction[])
    {
      final StructureFunction[] structureFunctions = (StructureFunction[]) maybeArray;
      return structureFunctions.length;
    }
    return 0;
  }

  /**
   * Returns the structure function at the given position.
   *
   * @param index the position of the function in the list.
   * @return the function, never null.
   * @throws IndexOutOfBoundsException if the index is invalid.
   */
  public StructureFunction getStructureFunction(final int index)
  {
    final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS);
    if (maybeArray instanceof StructureFunction[])
    {
      final StructureFunction[] structureFunctions = (StructureFunction[]) maybeArray;
      return structureFunctions[index];
    }
    throw new IndexOutOfBoundsException();
  }

  /**
   * Removes the given function from the collection of structure functions. This removes only the first occurence of the
   * function, in case a function has been added twice.
   *
   * @param f the function to be removed.
   */
  public void removeStructureFunction(final StructureFunction f)
  {
    if (f == null)
    {
      throw new NullPointerException();
    }

    final StructureFunction[] structureFunctions = getStructureFunctions();
    final ArrayList newProcessors = new ArrayList(Math.max(10, structureFunctions.length));
    boolean found = false;
    for (int i = 0; i < structureFunctions.length; i++)
    {
      final StructureFunction structureFunction = structureFunctions[i];
      if (found || structureFunction != f)
      {
        newProcessors.add(structureFunction);
        found = true;
      }
    }
    if (found)
    {
      final StructureFunction[] newArray =
          (StructureFunction[]) newProcessors.toArray(new StructureFunction[newProcessors.size()]);
      setAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS, newArray);
    }
  }

  /**
   * Returns a copy of all structure functions contained in the report. Modifying the function instances will not alter
   * the functions contained in the report.
   *
   * @return the functions.
   */
  public StructureFunction[] getStructureFunctions()
  {
    final Object maybeArray = getAttribute(AttributeNames.Internal.NAMESPACE, AttributeNames.Internal.STRUCTURE_FUNCTIONS);
    if (maybeArray instanceof StructureFunction[])
    {
      final StructureFunction[] structureFunctions = (StructureFunction[]) maybeArray;
      return (StructureFunction[]) structureFunctions.clone();
    }
    return new StructureFunction[0];
  }

  /**
   * A helper method that deserializes a object from the given stream.
   *
   * @param stream the stream from which to read the object data.
   * @throws IOException            if an IO error occured.
   * @throws ClassNotFoundException if an referenced class cannot be found.
   */
  private void readObject(final ObjectInputStream stream)
      throws IOException, ClassNotFoundException
  {
    stream.defaultReadObject();
    updateResourceBundleFactory();
  }

  protected void updateResourceBundleFactory()
  {
    if (resourceBundleFactory instanceof ExtendedResourceBundleFactory)
    {
      final ExtendedResourceBundleFactory erbf = (ExtendedResourceBundleFactory) resourceBundleFactory;
      erbf.setResourceLoader(getResourceManager(), getContentBase());
    }

  }

  /**
   * Listens for changes to the DocumentBundle being used by a report and will update the ResourceManager to use that
   * DocumentBundle.
   */
  private static class ResourceBundleChangeHandler implements ReportModelListener
  {
    private ResourceBundleChangeHandler()
    {
    }

    public void nodeChanged(final ReportModelEvent event)
    {
      final Object element = event.getElement();
      if (element instanceof AbstractReportDefinition == false)
      {
        return;
      }
      if (event.isNodeStructureChanged())
      {
        return;
      }
      if (event.getParameter() instanceof StyleChange)
      {
        return;
      }
      final AbstractReportDefinition report = (AbstractReportDefinition) element;
      report.updateResourceBundleFactory();
    }
  }
}
TOP

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

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.