/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core; import org.pentaho.reporting.engine.classic.core.filter.types.bands.BandType; import org.pentaho.reporting.engine.classic.core.style.BandDefaultStyleSheet; import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys; import org.pentaho.reporting.engine.classic.core.style.ElementStyleSheet; import org.pentaho.reporting.engine.classic.core.util.InstanceID; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * A report band is a collection of other elements and bands, similiar to an AWT-Container. * <p/> * This implementation is not synchronized, to take care that you externally synchronize it when using multiple threads * to modify instances of this class. * <p/> * Trying to add a parent of an band as child to the band, will result in an exception. * * @author David Gilbert * @author Thomas Morgner */ public class Band extends Section { /** * An empty array to prevent object creation. */ private static final Element[] EMPTY_ARRAY = new Element[0]; /** * All the elements for this band. */ private ArrayList<Element> allElements; /** * Cached elements. */ private transient Element[] allElementsCached; /** * The prefix for anonymous bands, bands without an userdefined name. */ public static final String ANONYMOUS_BAND_PREFIX = "anonymousBand@"; /** * Constructs a new band (initially empty). */ public Band() { setElementType( new BandType() ); } public Band( final InstanceID id ) { super( id ); setElementType( new BandType() ); } /** * Constructs a new band with the given pagebreak attributes. Pagebreak attributes have no effect on subbands. * * @param pagebreakAfter * defines, whether a pagebreak should be done after that band was printed. * @param pagebreakBefore * defines, whether a pagebreak should be done before that band gets printed. */ public Band( final boolean pagebreakBefore, final boolean pagebreakAfter ) { this(); if ( pagebreakBefore ) { setPagebreakBeforePrint( pagebreakBefore ); } if ( pagebreakAfter ) { setPagebreakAfterPrint( pagebreakAfter ); } } /** * Returns the global stylesheet for all bands. This stylesheet provides the predefined default values for some of the * stylekeys. * * @return the global default stylesheet. */ public ElementStyleSheet getDefaultStyleSheet() { return BandDefaultStyleSheet.getBandDefaultStyle(); } /** * Adds a report element to the band. * * @param element * the element that should be added * @throws NullPointerException * if the given element is null * @throws IllegalArgumentException * if the position is invalid, either negative or greater than the number of elements in this band or if the * given element is a parent of this element. */ public void addElement( final Element element ) { addElement( getElementCount(), element ); } /** * Adds a report element to the band. The element will be inserted at the specified position. * * @param position * the position where to insert the element * @param element * the element that should be added * @throws NullPointerException * if the given element is null * @throws IllegalArgumentException * if the position is invalid, either negative or greater than the number of elements in this band or if the * given element is a parent of this element. */ public void addElement( final int position, final Element element ) { if ( position < 0 ) { throw new IllegalArgumentException( "Position < 0" ); } if ( position > getElementCount() ) { throw new IllegalArgumentException( "Position < 0" ); } if ( element == null ) { throw new NullPointerException( "Band.addElement(...): element is null." ); } validateLooping( element ); if ( unregisterParent( element ) ) { return; } if ( allElements == null ) { allElements = new ArrayList<Element>(); } // add the element, update the childs Parent and the childs stylesheet. allElements.add( position, element ); allElementsCached = null; // then add the parents, or the band's parent will be unregistered .. registerAsChild( element ); notifyNodeChildAdded( element ); } /** * Adds a collection of elements to the band. * * @param elements * the element collection. * @throws NullPointerException * if one of the given elements is null * @throws IllegalArgumentException * if one of the given element is a parent of this element. */ public void addElements( final Collection elements ) { if ( elements == null ) { throw new NullPointerException( "Band.addElements(...): collection is null." ); } final Iterator iterator = elements.iterator(); while ( iterator.hasNext() ) { final Element element = (Element) iterator.next(); addElement( element ); } } /** * Returns the first element in the list that is known by the given name. Functions should use * {@link org.pentaho.reporting.engine.classic.core.function.FunctionUtilities#findAllElements(Band, String)} or * {@link org.pentaho.reporting.engine.classic.core.function.FunctionUtilities#findElement(Band, String)} instead. * * @param name * the element name. * @return the first element with the specified name, or <code>null</code> if there is no such element. * @throws NullPointerException * if the given name is null. */ public Element getElement( final String name ) { if ( name == null ) { throw new NullPointerException( "Band.getElement(...): name is null." ); } final Element[] elements = internalGetElementArray(); final int elementsSize = elements.length; for ( int i = 0; i < elementsSize; i++ ) { final Element e = elements[i]; final String elementName = e.getName(); if ( elementName != null ) { if ( elementName.equals( name ) ) { return e; } } } return null; } /** * Removes an element from the band. * * @param e * the element to be removed. * @throws NullPointerException * if the given element is null. */ public void removeElement( final Element e ) { if ( e == null ) { throw new NullPointerException(); } if ( e.getParentSection() != this ) { // this is none of my childs, ignore the request ... return; } if ( allElements == null ) { return; } e.setParent( null ); allElements.remove( e ); allElementsCached = null; notifyNodeChildRemoved( e ); } public void removeElement( int index ) { removeElement( getElement( index ) ); } public void setElementAt( final int position, final Element element ) { if ( position < 0 ) { throw new IllegalArgumentException( "Position < 0" ); } if ( position >= getElementCount() ) { throw new IllegalArgumentException( "Position >= size" ); } if ( element == null ) { throw new NullPointerException( "Band.addElement(...): element is null." ); } validateLooping( element ); if ( unregisterParent( element ) ) { return; } if ( allElements == null ) { throw new IllegalStateException( "The throws above should have caught that state" ); } // add the element, update the childs Parent and the childs stylesheet. final Element o = allElements.set( position, element ); o.setParent( null ); allElementsCached = null; // then add the parents, or the band's parent will be unregistered .. registerAsChild( element ); notifyNodeChildRemoved( o ); notifyNodeChildAdded( element ); } public void clear() { final Element[] elements = internalGetElementArray(); for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[i]; removeElement( element ); } } /** * Returns the number of elements in this band. * * @return the number of elements of this band. */ public int getElementCount() { if ( allElements == null ) { return 0; } return allElements.size(); } /** * Returns an array of the elements in the band. If the band is empty, an empty array is returned. * <p/> * Implementation note: The array returned is a copy of the internal backend. Any modification of the array will no * longer result in modifications of the internal object state. To avoid unneccessary object creations, you can use * the {@link Band#unsafeGetElementArray()} method now. * * @return the elements. */ public Element[] getElementArray() { return internalGetElementArray().clone(); } /** * An internal method that allows other internal methods to work with the uncloned backend. * * @return the elements as array. */ private Element[] internalGetElementArray() { if ( allElementsCached == null ) { if ( allElements == null || allElements.isEmpty() ) { allElementsCached = Band.EMPTY_ARRAY; } else { Element[] elements = new Element[allElements.size()]; elements = allElements.toArray( elements ); allElementsCached = elements; } } return allElementsCached; } public final Element[] unsafeGetElementArray() { return internalGetElementArray(); } /** * Returns the element stored add the given index. * * @param index * the element position within this band * @return the element * @throws IndexOutOfBoundsException * if the index is invalid. */ public Element getElement( final int index ) { if ( allElements == null ) { throw new IndexOutOfBoundsException( "This index is invalid." ); } return allElements.get( index ); } /** * Returns a string representation of the band, useful mainly for debugging purposes. * * @return a string representation of this band. */ public String toString() { final StringBuilder b = new StringBuilder( 100 ); b.append( this.getClass().getName() ); b.append( "={name=\"" ); b.append( getName() ); b.append( "\", size=\"" ); b.append( getElementCount() ); b.append( "\", layout=\"" ); b.append( getStyle().getStyleProperty( BandStyleKeys.LAYOUT ) ); b.append( "\"}" ); return b.toString(); } /** * Clones this band and all elements contained in this band. After the cloning the band is no longer connected to a * report definition. * * @return the clone of this band. */ public Band clone() { final Band b = (Band) super.clone(); if ( allElements != null ) { final int elementSize = allElements.size(); b.allElements = (ArrayList<Element>) allElements.clone(); b.allElements.clear(); b.allElementsCached = new Element[elementSize]; if ( allElementsCached != null ) { for ( int i = 0; i < elementSize; i++ ) { final Element eC = (Element) allElementsCached[i].clone(); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } else { for ( int i = 0; i < elementSize; i++ ) { final Element e = allElements.get( i ); final Element eC = (Element) e.clone(); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } } return b; } /** * Creates a deep copy of this element and regenerates all instance-ids. * * @return the copy of the element. */ public Band derive( final boolean preserveElementInstanceIds ) { final Band b = (Band) super.derive( preserveElementInstanceIds ); if ( allElements != null ) { final int elementSize = allElements.size(); b.allElements = (ArrayList<Element>) allElements.clone(); b.allElements.clear(); b.allElementsCached = new Element[elementSize]; if ( allElementsCached != null ) { for ( int i = 0; i < elementSize; i++ ) { final Element eC = allElementsCached[i].derive( preserveElementInstanceIds ); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } else { for ( int i = 0; i < elementSize; i++ ) { final Element e = allElements.get( i ); final Element eC = e.derive( preserveElementInstanceIds ); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } } return b; } /** * Returns, whether the page layout manager should perform a pagebreak before this page is printed. This will have no * effect on empty pages or if the band is no root-level band. * * @return true, if to force a pagebreak before this band is printed, false otherwise */ public boolean isPagebreakBeforePrint() { return getStyle().getBooleanStyleProperty( BandStyleKeys.PAGEBREAK_BEFORE ); } /** * Defines, whether the page layout manager should perform a pagebreak before this page is printed. This will have no * effect on empty pages or if the band is no root-level band. * * @param pagebreakBeforePrint * set to true, if to force a pagebreak before this band is printed, false otherwise */ public void setPagebreakBeforePrint( final boolean pagebreakBeforePrint ) { getStyle().setBooleanStyleProperty( BandStyleKeys.PAGEBREAK_BEFORE, pagebreakBeforePrint ); notifyNodePropertiesChanged(); } /** * Returns, whether the page layout manager should perform a pagebreak before this page is printed. This will have no * effect on empty pages or if the band is no root-level band. * * @return true, if to force a pagebreak before this band is printed, false otherwise */ public boolean isPagebreakAfterPrint() { return getStyle().getBooleanStyleProperty( BandStyleKeys.PAGEBREAK_AFTER ); } /** * Defines, whether the page layout manager should perform a pagebreak before this page is printed. This will have no * effect on empty pages or if the band is no root-level band. * * @param pagebreakAfterPrint * set to true, if to force a pagebreak before this band is printed, false otherwise */ public void setPagebreakAfterPrint( final boolean pagebreakAfterPrint ) { getStyle().setBooleanStyleProperty( BandStyleKeys.PAGEBREAK_AFTER, pagebreakAfterPrint ); notifyNodePropertiesChanged(); } public void setLayout( final String layout ) { getStyle().setStyleProperty( BandStyleKeys.LAYOUT, layout ); notifyNodePropertiesChanged(); } public String getLayout() { return (String) getStyle().getStyleProperty( BandStyleKeys.LAYOUT ); } }