/* * 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 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; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; 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 ); } }