/*
* 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) 2006 - 2009 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.designer.core.editor.crosstab;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.model.ModelUtility;
import org.pentaho.reporting.designer.core.util.undo.UndoEntry;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.AttributeNames;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.CrosstabCell;
import org.pentaho.reporting.engine.classic.core.CrosstabCellBody;
import org.pentaho.reporting.engine.classic.core.CrosstabColumnGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabOtherGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabRowGroup;
import org.pentaho.reporting.engine.classic.core.DetailsHeader;
import org.pentaho.reporting.engine.classic.core.Element;
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.RelationalGroup;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.SubGroupBody;
import org.pentaho.reporting.engine.classic.core.dom.AndMatcher;
import org.pentaho.reporting.engine.classic.core.dom.AttributeMatcher;
import org.pentaho.reporting.engine.classic.core.dom.ElementMatcher;
import org.pentaho.reporting.engine.classic.core.dom.MatcherContext;
import org.pentaho.reporting.engine.classic.core.dom.NodeMatcher;
import org.pentaho.reporting.engine.classic.core.dom.ReportStructureMatcher;
import org.pentaho.reporting.engine.classic.core.elementfactory.CrosstabBuilder;
import org.pentaho.reporting.engine.classic.core.elementfactory.CrosstabDetail;
import org.pentaho.reporting.engine.classic.core.elementfactory.CrosstabDimension;
import org.pentaho.reporting.engine.classic.core.filter.types.LabelType;
import org.pentaho.reporting.engine.classic.core.function.AggregationFunction;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.engine.classic.core.wizard.ContextAwareDataSchemaModel;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
public final class CrosstabEditSupport
{
public static class EditGroupOnReportUndoEntry implements UndoEntry
{
private static final long serialVersionUID = -6048384734272767240L;
private Group newRootGroup;
private Group oldRootGroup;
public EditGroupOnReportUndoEntry(final Group oldRootGroup, final Group newRootGroup)
{
this.oldRootGroup = oldRootGroup.derive(true);
this.newRootGroup = newRootGroup.derive(true);
}
public void undo(final ReportDocumentContext renderContext)
{
final AbstractReportDefinition report = renderContext.getReportDefinition();
report.setRootGroup(oldRootGroup.derive(true));
}
public void redo(final ReportDocumentContext renderContext)
{
final AbstractReportDefinition report = renderContext.getReportDefinition();
report.setRootGroup(newRootGroup.derive(true));
}
public UndoEntry merge(final UndoEntry newEntry)
{
return null;
}
}
public static class EditGroupOnGroupUndoEntry implements UndoEntry
{
private InstanceID target;
private Group newRootGroup;
private Group oldRootGroup;
public EditGroupOnGroupUndoEntry(final InstanceID target,
final Group oldRootGroup,
final Group newRootGroup)
{
ArgumentNullException.validate("target", target);
ArgumentNullException.validate("oldRootGroup", oldRootGroup);
ArgumentNullException.validate("newRootGroup", newRootGroup);
this.target = target;
this.oldRootGroup = oldRootGroup.derive(true);
this.newRootGroup = newRootGroup.derive(true);
}
public void undo(final ReportDocumentContext renderContext)
{
final SubGroupBody bodyElement = (SubGroupBody)
ModelUtility.findElementById(renderContext.getReportDefinition(), target);
if (bodyElement == null)
{
throw new IllegalStateException("Expected to find a sub-group-body on the specified ID.");
}
bodyElement.setGroup(oldRootGroup.derive(true));
}
public void redo(final ReportDocumentContext renderContext)
{
final SubGroupBody bodyElement = (SubGroupBody)
ModelUtility.findElementById(renderContext.getReportDefinition(), target);
if (bodyElement == null)
{
throw new IllegalStateException("Expected to find a sub-group-body on this report.");
}
bodyElement.setGroup(newRootGroup.derive(true));
}
public UndoEntry merge(final UndoEntry newEntry)
{
return null;
}
}
public static class DetailsDefinition
{
private Element labelElement;
private Element detailElement;
private String field;
private Class<AggregationFunction> aggregationFunction;
private DetailsDefinition(final Element labelElement,
final Element detailElement,
final String field,
final Class<AggregationFunction> aggregationFunction)
{
this.labelElement = labelElement;
this.detailElement = detailElement;
this.field = field;
this.aggregationFunction = aggregationFunction;
}
public Element getLabelElement()
{
return labelElement;
}
public Element getDetailElement()
{
return detailElement;
}
public String getField()
{
return field;
}
public Class<AggregationFunction> getAggregationFunction()
{
return aggregationFunction;
}
public CrosstabDetail createDetail()
{
final String label = (String) labelElement.getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE);
return new CrosstabDetail(field, label, aggregationFunction);
}
}
private CrosstabEditSupport()
{
}
public static GroupDataBody installCrosstabIntoLastGroup(final RelationalGroup selectedGroup,
final CrosstabGroup newGroup)
{
final GroupDataBody oldBody = (GroupDataBody) selectedGroup.getBody();
// install the new crosstab group into the group
selectedGroup.setBody(new SubGroupBody(newGroup));
return oldBody;
}
private static void populateOptions(final LinkedHashMap<String, DetailsDefinition> cellBody,
final CrosstabBuilder builder)
{
builder.setMaximumHeight(null);
builder.setMaximumWidth(null);
builder.setPrefHeight(null);
builder.setPrefWidth(null);
builder.setMinimumHeight(new Float(-100));
builder.setMinimumWidth(new Float(-100));
Collection<DetailsDefinition> values = cellBody.values();
Boolean allowMetaAttrs = null;
Boolean allowMetaStyle = null;
for (final DetailsDefinition value : values)
{
Element detailElement = value.getDetailElement();
if (detailElement != null)
{
if (allowMetaAttrs == null)
{
allowMetaAttrs = detailElement.getAttributeTyped
(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ALLOW_METADATA_ATTRIBUTES, Boolean.class);
}
if (allowMetaStyle == null)
{
allowMetaStyle = detailElement.getAttributeTyped
(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.ALLOW_METADATA_STYLING, Boolean.class);
}
}
}
builder.setAllowMetaDataAttributes(allowMetaAttrs);
builder.setAllowMetaDataStyling(allowMetaStyle);
// todo: v2 - try to restore the active settings for width and heights
}
public static CrosstabBuilder populateBuilder(final CrosstabGroup editedGroup,
final ContextAwareDataSchemaModel reportDataSchemaModel)
{
CrosstabCellBody cellBody = null;
Group group = editedGroup.getBody().getGroup();
ArrayList<CrosstabRowGroup> rows = new ArrayList<CrosstabRowGroup>();
ArrayList<CrosstabColumnGroup> cols = new ArrayList<CrosstabColumnGroup>();
ArrayList<CrosstabOtherGroup> others = new ArrayList<CrosstabOtherGroup>();
while (group != null)
{
if (group instanceof CrosstabOtherGroup)
{
CrosstabOtherGroup otherGroup = (CrosstabOtherGroup) group;
others.add(otherGroup);
}
else if (group instanceof CrosstabRowGroup)
{
CrosstabRowGroup rowGroup = (CrosstabRowGroup) group;
rows.add(rowGroup);
}
else if (group instanceof CrosstabColumnGroup)
{
CrosstabColumnGroup colGroup = (CrosstabColumnGroup) group;
cols.add(colGroup);
}
else
{
break;
}
GroupBody body = group.getBody();
if (body instanceof CrosstabCellBody)
{
cellBody = (CrosstabCellBody) body;
break;
}
group = body.getGroup();
}
if (cellBody == null)
{
throw new IllegalStateException("A crosstab group can never be without a cell body");
}
LinkedHashMap<String, DetailsDefinition> details;
CrosstabCell element = cellBody.findElement(null, null);
if (element != null)
{
details = extractFromDetailCell(element, cellBody.getHeader());
}
else
{
details = new LinkedHashMap<String, DetailsDefinition>();
}
final CrosstabEditorBuilder builder = new CrosstabEditorBuilder(reportDataSchemaModel, cellBody, details);
populateOptions(details, builder);
for (final CrosstabOtherGroup other : others)
{
builder.addOtherDimension(other);
}
for (final CrosstabRowGroup row : rows)
{
builder.addRowDimension(extractFromRowGroup(row), row);
}
for (final CrosstabColumnGroup col : cols)
{
builder.addColumnDimension(extractFromColumnGroup(col), col);
}
for (final DetailsDefinition value : details.values())
{
builder.addDetails(value.createDetail());
}
return builder;
}
private static LinkedHashMap<String, DetailsDefinition> extractFromDetailCell(final CrosstabCell cell,
final DetailsHeader header)
{
ReportElement[] elementsByAttribute =
ReportStructureMatcher.findElementsByAttribute(cell, AttributeNames.Core.NAMESPACE, AttributeNames.Core.FIELD);
final LinkedHashMap<String, DetailsDefinition> d = new LinkedHashMap<String, DetailsDefinition>();
for (final ReportElement e : elementsByAttribute)
{
String field = (String) e.getAttribute(AttributeNames.Core.NAMESPACE, AttributeNames.Core.FIELD);
Class agg = (Class) e.getAttribute(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.AGGREGATION_TYPE);
if (!AggregationFunction.class.isAssignableFrom(agg))
{
agg = null;
}
ReportElement[] labels = ReportStructureMatcher.findElementsByAttribute
(header, AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.LABEL_FOR, field);
Element label;
if (labels.length > 0)
{
label = (Element) labels[0];
}
else
{
label = null;
}
d.put(field, new DetailsDefinition(label, (Element) e, field, agg));
}
return d;
}
private static CrosstabDimension extractFromRowGroup(final CrosstabRowGroup rowGroup)
{
final String title = findTitle(rowGroup.getField(), rowGroup.getTitleHeader());
final String summaryTitle = findTitle(rowGroup.getField(), rowGroup.getSummaryHeader());
final boolean summary = rowGroup.isPrintSummary();
return new CrosstabDimension(rowGroup.getField(), title, summary, summaryTitle);
}
private static String findTitle(final String field, final Band titleHeader)
{
final MatcherContext context = new MatcherContext();
context.setMatchSubReportChilds(false);
NodeMatcher m = new AndMatcher(new ElementMatcher(LabelType.INSTANCE),
new AttributeMatcher(AttributeNames.Wizard.NAMESPACE, AttributeNames.Wizard.LABEL_FOR, field));
ReportElement match = ReportStructureMatcher.match(context, titleHeader, m);
if (match == null)
{
if (titleHeader.getElementCount() > 0)
{
Element e = titleHeader.getElement(0);
if (e.getElementType() instanceof LabelType)
{
return e.getAttributeTyped(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE, String.class);
}
}
return null;
}
return match.getAttributeTyped(AttributeNames.Core.NAMESPACE, AttributeNames.Core.VALUE, String.class);
}
private static CrosstabDimension extractFromColumnGroup(final CrosstabColumnGroup rowGroup)
{
final String title = findTitle(rowGroup.getField(), rowGroup.getTitleHeader());
final String summaryTitle = findTitle(rowGroup.getField(), rowGroup.getSummaryHeader());
final boolean summary = rowGroup.isPrintSummary();
return new CrosstabDimension(rowGroup.getField(), title, summary, summaryTitle);
}
}