/*! * 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) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core; import org.pentaho.reporting.engine.classic.core.filter.types.bands.CrosstabCellBodyType; import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys; import org.pentaho.reporting.engine.classic.core.style.ElementStyleKeys; import java.util.ArrayList; public class CrosstabCellBody extends GroupBody { private static final CrosstabCell[] EMPTY_ARRAY = new CrosstabCell[0]; private ArrayList<CrosstabCell> allElements; private transient CrosstabCell[] allElementsCached; private DetailsHeader detailsHeader; public CrosstabCellBody() { setElementType( CrosstabCellBodyType.INSTANCE ); getStyle().setStyleProperty( ElementStyleKeys.AVOID_PAGEBREAK_INSIDE, Boolean.FALSE ); detailsHeader = new DetailsHeader(); registerAsChild( detailsHeader ); } public Group getGroup() { return null; } /** * Returns the group header. * <P> * The group header is a report band that contains elements that should be printed at the start of a group. * * @return the group header. */ public DetailsHeader getHeader() { return detailsHeader; } /** * Sets the header for the group. * * @param header * the header (null not permitted). * @throws NullPointerException * if the given header is null */ public void setHeader( final DetailsHeader header ) { if ( header == null ) { throw new NullPointerException( "Header must not be null" ); } validateLooping( header ); if ( unregisterParent( header ) ) { return; } final Element element = this.detailsHeader; this.detailsHeader.setParent( null ); this.detailsHeader = header; this.detailsHeader.setParent( this ); notifyNodeChildRemoved( element ); notifyNodeChildAdded( this.detailsHeader ); } /** * 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 CrosstabCell 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 CrosstabCell element ) { if ( position < 1 ) { throw new IllegalArgumentException( "Position < 1" ); } if ( position > getElementCount() ) { throw new IllegalArgumentException( "Position > ElementCount" ); } if ( element == null ) { throw new NullPointerException( "Band.addElement(...): element is null." ); } validateLooping( element ); if ( unregisterParent( element ) ) { return; } if ( allElements == null ) { allElements = new ArrayList<CrosstabCell>(); } // add the element, update the childs Parent and the childs stylesheet. allElements.add( position - 1, element ); allElementsCached = null; // then add the parents, or the band's parent will be unregistered .. registerAsChild( element ); notifyNodeChildAdded( element ); } /** * Returns the matching crosstab-cell for the given key set. When searching a detail cell, give an empty set. When * searching for a column-summary cell, give the column group field. Same for row-summary-cells. For total cells, give * both the column and row field. * * @param rowKeys * the known row-keys for the lookup. * @param colKeys * the known col-keys for the lookup. * @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 CrosstabCell findElement( final String rowKeys, final String colKeys ) { final CrosstabCell[] elements = internalGetElementArray(); final int elementsSize = elements.length; for ( int i = 0; i < elementsSize; i++ ) { final CrosstabCell e = elements[i]; if ( e == null ) { continue; } final String cellColField = e.getColumnField(); final String cellRowField = e.getRowField(); final boolean colFieldMatch = equalString( cellColField, colKeys ); final boolean rowFieldMatch = equalString( cellRowField, rowKeys ); if ( colFieldMatch && rowFieldMatch ) { return e; } } return null; } private boolean equalString( final String s1, final String s2 ) { if ( s1 != null ) { return s1.equals( s2 ); } else { return s2 == 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 ); // noinspection SuspiciousMethodCalls allElements.remove( e ); allElementsCached = null; notifyNodeChildRemoved( e ); } 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." ); } if ( position == 0 ) { if ( element instanceof DetailsHeader == false ) { throw new IllegalArgumentException(); } setHeader( (DetailsHeader) element ); return; } if ( element instanceof CrosstabCell == false ) { throw new IllegalArgumentException(); } validateLooping( element ); if ( unregisterParent( element ) ) { return; } final int insertPosition = position - 1; 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( insertPosition, (CrosstabCell) 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 1; } return 1 + allElements.size(); } /** * An internal method that allows other internal methods to work with the uncloned backend. * * @return the elements as array. */ private CrosstabCell[] internalGetElementArray() { if ( allElementsCached == null ) { if ( allElements == null || allElements.isEmpty() ) { allElementsCached = EMPTY_ARRAY; } else { CrosstabCell[] elements = new CrosstabCell[allElements.size()]; elements = allElements.toArray( elements ); allElementsCached = elements; } } return allElementsCached; } /** * 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 ( index == 0 ) { return detailsHeader; } if ( allElements == null ) { throw new IndexOutOfBoundsException( "This index is invalid." ); } return allElements.get( index - 1 ); } /** * 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 CrosstabCellBody clone() { final CrosstabCellBody b = (CrosstabCellBody) super.clone(); b.detailsHeader = (DetailsHeader) detailsHeader.clone(); b.registerAsChild( b.detailsHeader ); if ( allElements != null ) { final int elementSize = allElements.size(); b.allElements = (ArrayList<CrosstabCell>) allElements.clone(); b.allElements.clear(); b.allElementsCached = new CrosstabCell[elementSize]; if ( allElementsCached != null ) { for ( int i = 0; i < elementSize; i++ ) { final CrosstabCell eC = (CrosstabCell) allElementsCached[i].clone(); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } else { for ( int i = 0; i < elementSize; i++ ) { final CrosstabCell e = allElements.get( i ); final CrosstabCell eC = (CrosstabCell) 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 CrosstabCellBody derive( final boolean preserveElementInstanceIds ) { final CrosstabCellBody b = (CrosstabCellBody) super.derive( preserveElementInstanceIds ); b.detailsHeader = (DetailsHeader) detailsHeader.derive( preserveElementInstanceIds ); b.registerAsChild( b.detailsHeader ); if ( allElements != null ) { final int elementSize = allElements.size(); b.allElements = (ArrayList<CrosstabCell>) allElements.clone(); b.allElements.clear(); b.allElementsCached = new CrosstabCell[elementSize]; if ( allElementsCached != null ) { for ( int i = 0; i < elementSize; i++ ) { final CrosstabCell eC = (CrosstabCell) allElementsCached[i].derive( preserveElementInstanceIds ); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } else { for ( int i = 0; i < elementSize; i++ ) { final CrosstabCell e = allElements.get( i ); final CrosstabCell eC = (CrosstabCell) e.derive( preserveElementInstanceIds ); b.allElements.add( eC ); b.allElementsCached[i] = eC; eC.setParent( b ); } } } return b; } }