/*
* 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 java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
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;
/**
* 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);
}
}