/*
* 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.modules.parser.base;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.DetailsFooter;
import org.pentaho.reporting.engine.classic.core.DetailsHeader;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.GroupBody;
import org.pentaho.reporting.engine.classic.core.GroupDataBody;
import org.pentaho.reporting.engine.classic.core.ItemBand;
import org.pentaho.reporting.engine.classic.core.NoDataBand;
import org.pentaho.reporting.engine.classic.core.RelationalGroup;
import org.pentaho.reporting.engine.classic.core.SubGroupBody;
import org.pentaho.reporting.engine.classic.core.filter.types.bands.GroupDataBodyType;
import org.pentaho.reporting.libraries.xmlns.parser.ParseException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
/**
* The group list is used to store groups in a ordered way. The less specific groups are guaranteed to be listed before
* the more specific subgroups.
* <p/>
* Groups are ordered by comparing the declared fieldnames for the groups. A subgroup of an group must contain all
* fields from its parent plus at least one new field.
* <p/>
* This implementation is not synchronized.
* <p/>
* The group list cannot be empty. JFreeReport needs at least one group instance to work as expected. By default, this
* default instance does not define any fields (and therefore contains the complete report) and has no Bands defined
* (rendering it invisible). You cannot remove that group. Every attempt to remove the last group will recreates a new
* default group.
* <p/>
* As of version 0.8.10, this list only exists for the support for the legacy parsing.
*
* @author Thomas Morgner
* @deprecated The group-list is a legacy class and should not be used outside the legacy handling.
*/
public class GroupList implements Cloneable, Serializable {
/**
* A unique identifier for long term persistance.
*/
private static final long serialVersionUID = 2193162824440886046L;
/**
* Cache.
*/
private transient RelationalGroup[] cache;
/**
* The backend to store the groups.
*/
private ArrayList backend;
/**
* The name of the automaticly created default group.
*/
public static final String DEFAULT_GROUP_NAME = "default";
/**
* Constructs a new group list, with only a default group inside.
*/
public GroupList() {
backend = new ArrayList();
createDefaultGroup();
}
/**
* Creates a default group. The default group has no fields defined and spans all fields of the report.
*/
private void createDefaultGroup() {
final RelationalGroup defaultGroup = new RelationalGroup();
add( defaultGroup );
}
/**
* Creates a new group list and copies the contents of the given grouplist. If the given group list was assigned with
* a report definition, then the new group list will share that registration.
*
* @param list
* groups to add to the list.
*/
protected GroupList( final GroupList list ) {
backend = new ArrayList();
backend.addAll( list.backend );
}
/**
* Returns the group at a given position in the list.
*
* @param i
* the position index (zero-based).
* @return the report group.
*/
public Group get( final int i ) {
if ( cache == null ) {
cache = (RelationalGroup[]) backend.toArray( new RelationalGroup[backend.size()] );
}
return cache[i];
}
/**
* Removes an group from the list.
*
* @param o
* the group that should be removed.
* @return a boolean indicating whether or not the object was removed.
* @throws NullPointerException
* if the given group object is null.
*/
public boolean remove( final RelationalGroup o ) {
if ( o == null ) {
throw new NullPointerException();
}
cache = null;
final int idxOf = backend.indexOf( o );
if ( idxOf == -1 ) {
// the object was not in the list ...
return false;
}
// it might as well be a group that looks like the one we have in the list
// so be sure that you modify the one, that was removed, and not the one given
// to us.
backend.remove( idxOf );
if ( backend.isEmpty() ) {
createDefaultGroup();
}
return true;
}
/**
* Clears the list.
*/
public void clear() {
backend.clear();
createDefaultGroup();
cache = null;
}
/**
* Adds a group to the list.
*
* @param o
* the group object.
*/
public void add( final RelationalGroup o ) {
if ( o == null ) {
throw new NullPointerException( "Try to add null" );
}
cache = null;
final int idxOf = backend.indexOf( o );
if ( idxOf != -1 ) {
// it might as well be a group that looks like the one we have in the list
// so be sure that you modify the one, that was removed, and not the one given
// to us.
backend.remove( idxOf );
}
// this is a linear search to find the correct insertation point ..
for ( int i = 0; i < backend.size(); i++ ) {
final RelationalGroup compareGroup = (RelationalGroup) backend.get( i );
// if the current group at index i is greater than the new group
if ( compareGroups( compareGroup, o ) > 0 ) {
// then insert the new one before the current group ..
backend.add( i, o );
return;
}
}
// finally, if this group is the smallest group ...
backend.add( o );
}
/**
* Adds all groups of the collection to this group list. This method will result in a ClassCastException if the
* collection does not contain Group objects.
*
* @param c
* the collection that contains the groups.
* @throws NullPointerException
* if the given collection is null.
* @throws ClassCastException
* if the collection does not contain groups.
*/
public void addAll( final Collection c ) {
final Iterator it = c.iterator();
while ( it.hasNext() ) {
add( (RelationalGroup) it.next() );
}
}
/**
* Clones the group list and all contained groups.
*
* @return a clone of this list.
* @throws CloneNotSupportedException
* if cloning the element failed.
* @see Cloneable
*/
public Object clone() throws CloneNotSupportedException {
final GroupList l = (GroupList) super.clone();
final Group[] groups = getGroupCache();
l.backend = (ArrayList) backend.clone();
l.backend.clear();
final int length = groups.length;
l.cache = new RelationalGroup[length];
for ( int i = 0; i < length; i++ ) {
final RelationalGroup group = (RelationalGroup) groups[i].clone();
l.backend.add( group );
l.cache[i] = group;
}
return l;
}
/**
* Returns an iterator for the groups of the list.
*
* @return An iterator over all groups of the list.
*/
public Iterator iterator() {
return Collections.unmodifiableList( backend ).iterator();
}
/**
* Returns the number of groups in the list.
*
* @return The number of groups in the list.
*/
public int size() {
return backend.size();
}
/**
* Returns a string representation of the list (useful for debugging).
*
* @return A string.
*/
public String toString() {
final StringBuffer b = new StringBuffer();
b.append( "GroupList={backend='" );
b.append( backend );
b.append( "'} " );
return b.toString();
}
/**
* Returns a direct reference to the group cache.
*
* @return the groups of this list as array.
*/
protected RelationalGroup[] getGroupCache() {
if ( cache == null ) {
cache = (RelationalGroup[]) backend.toArray( new RelationalGroup[backend.size()] );
}
return cache;
}
/**
* 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 Group getGroupByName( final String name ) {
if ( name == null ) {
// Groups cannot have a null-name
return null;
}
final Group[] cache = getGroupCache();
final int length = cache.length;
for ( int i = 0; i < length; i++ ) {
if ( name.equals( cache[i].getName() ) ) {
return cache[i];
}
}
return null;
}
/**
* Creates a hierarchical group structure and moves the data group body to the inner most group. This method is only
* guaranteed to work correctly if there is exactly one data-group.
*
* @return the constructed group.
*/
public Group constructRootGroup() {
final RelationalGroup[] cache = getGroupCache();
if ( cache.length == 0 ) {
return new RelationalGroup();
}
GroupDataBody dataBody = null;
final Group rootGroup = cache[0];
Group currentGroup = rootGroup;
for ( int i = 1; i < cache.length; i++ ) {
final Group g = cache[i];
final GroupBody body = currentGroup.getBody();
if ( body instanceof SubGroupBody ) {
final SubGroupBody sbody = (SubGroupBody) body;
sbody.setGroup( g );
} else {
dataBody = (GroupDataBody) currentGroup.getBody();
currentGroup.setBody( new SubGroupBody( g ) );
}
currentGroup = g;
}
if ( dataBody != null ) {
currentGroup.setBody( dataBody );
}
return rootGroup;
}
/**
* Compares two objects (required to be instances of the Group class). The group's field lists are compared, order of
* the fields does not matter.
* <p/>
* This method only exists for legacy reasons.
*
* @param g1
* the first group.
* @param g2
* the second group.
* @return an integer indicating the relative ordering of the two groups.
*/
private int compareGroups( final RelationalGroup g1, final RelationalGroup g2 ) {
final List fieldsGroup1 = g1.getFields();
final List fieldsGroup2 = g2.getFields();
/** Remove all element, which are in both lists, they are equal */
if ( fieldsGroup1.size() == fieldsGroup2.size() ) {
// both lists contain the same elements.
if ( fieldsGroup1.containsAll( fieldsGroup2 ) ) {
return 0;
} else {
// groups with the same number of -, but different fields, are not compareable.
throw new IllegalArgumentException(
"These groups are not comparable, as they don't have any subgroup relation. "
+ " Groups of the same GroupList must have a subgroup relation. The designated "
+ " child group must contain all fields of the direct parent plus at least one " + " new field." );
}
}
if ( fieldsGroup1.containsAll( fieldsGroup2 ) ) {
// c2 contains all elements of c1, so c1 is subgroup of c2
return 1;
}
if ( fieldsGroup2.containsAll( fieldsGroup1 ) ) {
// c1 contains all elements of c2, so c2 is subgroup of c1
return -1;
}
// not compareable, invalid groups
// return 0;
throw new IllegalArgumentException( "These groups are not comparable, as they don't have any subgroup relation. "
+ " Groups of the same GroupList must have a subgroup relation. The designated "
+ " child group must contain all fields of the direct parent plus at least one " + " new field." );
}
public void installIntoReport( final AbstractReportDefinition report ) throws ParseException {
final GroupDataBody originalGroupDataBody =
(GroupDataBody) report.getChildElementByType( GroupDataBodyType.INSTANCE );
if ( originalGroupDataBody == null ) {
throw new ParseException( "The report is not a relational report, cannot install relational detail sections here" );
}
final ItemBand ib = originalGroupDataBody.getItemBand();
final NoDataBand nd = originalGroupDataBody.getNoDataBand();
final DetailsHeader detailsHeader = originalGroupDataBody.getDetailsHeader();
final DetailsFooter detailsFooter = originalGroupDataBody.getDetailsFooter();
final Group newRootGroup = constructRootGroup();
if ( report.getRootGroup() == newRootGroup ) {
return;
}
report.setRootGroup( newRootGroup );
final GroupDataBody groupDataBody = (GroupDataBody) newRootGroup.getChildElementByType( GroupDataBodyType.INSTANCE );
if ( groupDataBody == null ) {
return;
}
groupDataBody.setDetailsFooter( detailsFooter );
groupDataBody.setDetailsHeader( detailsHeader );
groupDataBody.setItemBand( ib );
groupDataBody.setNoDataBand( nd );
}
}