/*! * 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.designer.core.editor.styles; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.designer.core.editor.ReportDocumentContext; import org.pentaho.reporting.designer.core.settings.WorkspaceSettings; import org.pentaho.reporting.designer.core.util.exceptions.UncaughtExceptionsModel; import org.pentaho.reporting.designer.core.util.table.GroupedName; import org.pentaho.reporting.designer.core.util.table.GroupingHeader; import org.pentaho.reporting.designer.core.util.table.TableStyle; import org.pentaho.reporting.designer.core.util.undo.CompoundUndoEntry; import org.pentaho.reporting.designer.core.util.undo.StyleEditUndoEntry; import org.pentaho.reporting.designer.core.util.undo.StyleExpressionEditUndoEntry; import org.pentaho.reporting.designer.core.util.undo.UndoEntry; import org.pentaho.reporting.designer.core.util.undo.UndoManager; import org.pentaho.reporting.engine.classic.core.Element; import org.pentaho.reporting.engine.classic.core.function.Expression; import org.pentaho.reporting.engine.classic.core.metadata.ElementType; import org.pentaho.reporting.engine.classic.core.metadata.GroupedMetaDataComparator; import org.pentaho.reporting.engine.classic.core.metadata.PlainMetaDataComparator; import org.pentaho.reporting.engine.classic.core.metadata.StyleMetaData; import org.pentaho.reporting.engine.classic.core.style.ElementStyleSheet; import org.pentaho.reporting.engine.classic.core.style.ResolverStyleSheet; import org.pentaho.reporting.engine.classic.core.style.resolver.SimpleStyleResolver; import org.pentaho.reporting.engine.classic.core.style.resolver.StyleResolver; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import javax.swing.*; import java.beans.PropertyEditor; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class StyleTableModel extends AbstractStyleTableModel<StyleTableModel.DefaultStyleDataBackend> { private static final Log logger = LogFactory.getLog( StyleTableModel.class ); private static final Object NULL_INDICATOR = new Object(); private static final Element[] EMPTY_ELEMENTS = new Element[ 0 ]; private static final ElementType[] EMPTY_ELEMENT_TYPES = new ElementType[ 0 ]; private static final Object[] EMPTY_VALUES = new Object[ 0 ]; protected static class DefaultStyleDataBackend extends AbstractStyleDataBackend { private Element[] elements; private ElementType[] elementTypes; private Object[] inheritValues; private Object[] expressionValues; private DefaultStyleDataBackend() { this.elements = EMPTY_ELEMENTS; this.elementTypes = EMPTY_ELEMENT_TYPES; this.inheritValues = EMPTY_VALUES; this.expressionValues = EMPTY_VALUES; } private DefaultStyleDataBackend( final StyleMetaData[] metaData, final GroupingHeader[] groupings, final Element[] elements ) { super( metaData, groupings ); this.elements = elements; this.elementTypes = new ElementType[ elements.length ]; for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[ i ]; elementTypes[ i ] = element.getElementType(); } final ResolverStyleSheet resolverStyleSheet = getResolvedStyle(); if ( elements.length > 0 ) { final StyleResolver resolver = new SimpleStyleResolver( true ); resolver.resolve( elements[ 0 ], resolverStyleSheet ); } this.inheritValues = new Object[ metaData.length ]; this.expressionValues = new Object[ metaData.length ]; } public void clearCache( final int rowIndex ) { super.clearCache( rowIndex ); inheritValues[ rowIndex ] = null; } public void resetCache() { super.resetCache(); Arrays.fill( inheritValues, null ); Arrays.fill( expressionValues, null ); final ResolverStyleSheet resolverStyleSheet = getResolvedStyle(); if ( elements.length > 0 ) { final StyleResolver resolver = new SimpleStyleResolver( true ); resolver.resolve( elements[ 0 ], resolverStyleSheet ); } else { resolverStyleSheet.clear(); } } public Element[] getData() { return elements; } public void clearExpressionsCache( final int rowIndex ) { expressionValues[ rowIndex ] = null; } public Object[] getInheritValues() { return inheritValues; } public Object[] getExpressionValues() { return expressionValues; } public ElementType[] getElementTypes() { return elementTypes; } } private class UpdateDataTask implements Runnable { private Element[] elements; private boolean synchronous; private UpdateDataTask( final Element[] elements, final boolean synchronous ) { this.synchronous = synchronous; this.elements = elements.clone(); } public void run() { try { final DefaultStyleDataBackend dataBackend = updateData( elements ); if ( synchronous || SwingUtilities.isEventDispatchThread() ) { setDataBackend( dataBackend ); fireTableDataChanged(); } else { SwingUtilities.invokeAndWait( new NotifyChangeTask( dataBackend ) ); } } catch ( Exception e ) { UncaughtExceptionsModel.getInstance().addException( e ); } } } private Executor pool; private DefaultStyleDataBackend oldDataBackend; private ReportDocumentContext reportRenderContext; public StyleTableModel() { this( Executors.newSingleThreadExecutor() ); } public StyleTableModel( final Executor pool ) { if ( pool == null ) { throw new NullPointerException(); } this.pool = pool; super.setDataBackend( new DefaultStyleDataBackend() ); } public void setTableStyle( final TableStyle tableStyle ) { super.setTableStyle( tableStyle ); pool.execute( new UpdateDataTask( getData(), isSynchronous() ) ); } public synchronized void setDataBackend( final DefaultStyleDataBackend dataBackend ) { this.oldDataBackend = getDataBackend(); super.setDataBackend( dataBackend ); } protected DefaultStyleDataBackend updateData( final Element[] elements ) { final StyleMetaData[] metaData = selectCommonAttributes( elements ); final TableStyle tableStyle = getTableStyle(); if ( tableStyle == TableStyle.ASCENDING ) { Arrays.sort( metaData, new PlainMetaDataComparator() ); return ( new DefaultStyleDataBackend( metaData, new GroupingHeader[ metaData.length ], elements ) ); } else if ( tableStyle == TableStyle.DESCENDING ) { Arrays.sort( metaData, Collections.reverseOrder( new PlainMetaDataComparator() ) ); return ( new DefaultStyleDataBackend( metaData, new GroupingHeader[ metaData.length ], elements ) ); } else { Arrays.sort( metaData, new GroupedMetaDataComparator() ); final Locale locale = Locale.getDefault(); int groupCount = 0; int metaDataCount = 0; if ( metaData.length > 0 ) { String oldValue = null; for ( int i = 0; i < metaData.length; i++ ) { final StyleMetaData data = metaData[ i ]; if ( data.isHidden() ) { continue; } if ( !WorkspaceSettings.getInstance().isVisible( data ) ) { continue; } metaDataCount += 1; if ( groupCount == 0 ) { groupCount = 1; final StyleMetaData firstdata = metaData[ i ]; oldValue = firstdata.getGrouping( locale ); continue; } final String grouping = data.getGrouping( locale ); if ( ( ObjectUtilities.equal( oldValue, grouping ) ) == false ) { oldValue = grouping; groupCount += 1; } } } final StyleMetaData[] groupedMetaData = new StyleMetaData[ metaDataCount + groupCount ]; int targetIdx = 0; GroupingHeader[] groupings = new GroupingHeader[ groupedMetaData.length ]; GroupingHeader group = null; for ( int sourceIdx = 0; sourceIdx < metaData.length; sourceIdx++ ) { final StyleMetaData data = metaData[ sourceIdx ]; if ( data.isHidden() ) { continue; } if ( !WorkspaceSettings.getInstance().isVisible( data ) ) { continue; } if ( targetIdx == 0 ) { group = new GroupingHeader( data.getGrouping( locale ) ); groupings[ targetIdx ] = group; targetIdx += 1; } else { final String newgroup = data.getGrouping( locale ); //noinspection ConstantConditions if ( ( ObjectUtilities.equal( newgroup, group.getHeaderText() ) ) == false ) { group = new GroupingHeader( newgroup ); groupings[ targetIdx ] = group; targetIdx += 1; } } groupings[ targetIdx ] = group; groupedMetaData[ targetIdx ] = data; targetIdx += 1; } if ( oldDataBackend != null ) { groupings = reconcileState( groupings, oldDataBackend.getGroupings() ); } return new DefaultStyleDataBackend( groupedMetaData, groupings, elements ); } } private static boolean isSameElements( final Element[] elements, final ElementType[] elementTypes, final Element[] oldElements ) { if ( elements.length != oldElements.length ) { // that is easy! return false; } for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[ i ]; if ( oldElements[ i ].getObjectID() != element.getObjectID() ) { return false; } if ( oldElements[ i ].getElementType() != elementTypes[ i ] ) { return false; } } return true; } protected static StyleMetaData[] selectCommonAttributes( final Element[] elements ) { final HashMap<String, Boolean> attributes = new HashMap<String, Boolean>(); final ArrayList<StyleMetaData> selectedArrays = new ArrayList<StyleMetaData>(); for ( int elementIdx = 0; elementIdx < elements.length; elementIdx++ ) { final Element element = elements[ elementIdx ]; final StyleMetaData[] datas = element.getMetaData().getStyleDescriptions(); for ( int styleIdx = 0; styleIdx < datas.length; styleIdx++ ) { final StyleMetaData data = datas[ styleIdx ]; final String name = data.getName(); if ( data.isHidden() ) { attributes.put( name, Boolean.FALSE ); continue; } if ( !WorkspaceSettings.getInstance().isVisible( data ) ) { attributes.put( name, Boolean.FALSE ); continue; } final Object attribute = attributes.get( name ); if ( Boolean.TRUE.equals( attribute ) ) { // fine, we already have a value for it. } else if ( attribute == null ) { // add it .. if ( elementIdx == 0 ) { selectedArrays.add( data ); attributes.put( name, Boolean.TRUE ); } else { attributes.put( name, Boolean.FALSE ); } } } } return selectedArrays.toArray( new StyleMetaData[ selectedArrays.size() ] ); } public void setData( final Element[] elements ) { final DefaultStyleDataBackend backend = this.getDataBackend(); if ( isSameElements( elements, backend.getElementTypes(), backend.getData() ) ) { if ( isSynchronous() ) { new SameElementsUpdateDataTask( backend, isSynchronous() ).run(); } else { SwingUtilities.invokeLater( new SameElementsUpdateDataTask( backend, isSynchronous() ) ); } return; } pool.execute( new UpdateDataTask( elements, isSynchronous() ) ); } public Element[] getData() { return getDataBackend().getData(); } public int getColumnCount() { return 4; } public String getColumnName( final int column ) { switch( column ) { case 0: return Messages.getString( "StyleTableModel.NameColumn" ); case 1: return Messages.getString( "StyleTableModel.InheritColumn" ); case 2: return Messages.getString( "StyleTableModel.ValueColumn" ); case 3: return Messages.getString( "StyleTableModel.FormulaColumn" ); default: throw new IllegalArgumentException(); } } public Object getValueAt( final int rowIndex, final int columnIndex ) { final StyleMetaData metaData = getMetaData( rowIndex ); if ( metaData == null ) { return getGroupings( rowIndex ); } switch( columnIndex ) { case 0: return new GroupedName( metaData ); case 1: return computeInheritValue( metaData, rowIndex ); case 2: return computeFullValue( metaData, rowIndex ); case 3: return computeExpressionValue( metaData, rowIndex ); default: throw new IndexOutOfBoundsException(); } } public boolean isCellEditable( final int rowIndex, final int columnIndex ) { final StyleMetaData metaData = getMetaData( rowIndex ); if ( metaData == null ) { return false; } switch( columnIndex ) { case 0: return false; case 1: return true; case 2: return true; case 3: return true; default: throw new IndexOutOfBoundsException(); } } public void setValueAt( final Object aValue, final int rowIndex, final int columnIndex ) { final StyleMetaData metaData = getMetaData( rowIndex ); if ( metaData == null ) { return; } switch( columnIndex ) { case 0: return; case 1: { if ( Boolean.TRUE.equals( aValue ) ) { if ( defineFullValue( metaData, null ) ) { getDataBackend().clearCache( rowIndex ); fireTableDataChanged(); } } break; } case 2: { if ( defineFullValue( metaData, aValue ) ) { getDataBackend().clearCache( rowIndex ); fireTableDataChanged(); } break; } case 3: { if ( aValue != null && aValue instanceof Expression == false ) { return; } if ( defineExpressionValue( metaData, (Expression) aValue ) ) { getDataBackend().clearExpressionsCache( rowIndex ); fireTableDataChanged(); } break; } default: throw new IndexOutOfBoundsException(); } } protected boolean defineFullValue( final StyleMetaData metaData, final Object value ) { if ( value != null && metaData.getTargetType().isInstance( value ) == false ) { // not the correct type logger.warn( "Invalid type: " + value + "(" + value.getClass() + ") but expected " + // NON-NLS metaData.getTargetType() ); return false; } boolean changed = false; final Element[] elements = getDataBackend().getData(); for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[ i ]; final ElementStyleSheet styleSheet = element.getStyle(); final Object attribute = styleSheet.getStyleProperty( metaData.getStyleKey() ); if ( ( ObjectUtilities.equal( attribute, value ) ) == false ) { changed = true; } } if ( changed ) { final ReportDocumentContext reportRenderContext = getReportRenderContext(); if ( reportRenderContext == null ) { throw new IllegalStateException( "No report render context? Thats bad." ); } final UndoManager undo = reportRenderContext.getUndo(); final ArrayList<UndoEntry> undos = new ArrayList<UndoEntry>(); for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[ i ]; final ElementStyleSheet styleSheet = element.getStyle(); final Object attribute = styleSheet.getStyleProperty( metaData.getStyleKey() ); undos.add( new StyleEditUndoEntry ( element.getObjectID(), metaData.getStyleKey(), attribute, value ) ); styleSheet.setStyleProperty( metaData.getStyleKey(), value ); } undo.addChange( Messages.getString( "StyleChange" ), new CompoundUndoEntry( (UndoEntry[]) undos.toArray( new UndoEntry[ undos.size() ] ) ) ); } return changed; } protected Object computeInheritValue( final StyleMetaData metaData, final int rowIndex ) { final DefaultStyleDataBackend dataBackend1 = getDataBackend(); final Object[] inheritValues = dataBackend1.getInheritValues(); final Object o = inheritValues[ rowIndex ]; if ( o == StyleDataBackend.NULL_INDICATOR ) { return null; } if ( o != null ) { return o; } boolean allLocalKeys = true; boolean allInheritedKeys = true; final Element[] elements = dataBackend1.getData(); if ( elements.length > 0 ) { final Element element = elements[ 0 ]; final ElementStyleSheet styleSheet = element.getStyle(); final boolean localKey = styleSheet.isLocalKey( metaData.getStyleKey() ); allLocalKeys = allLocalKeys & localKey; allInheritedKeys = ( localKey == false ); } final Object retval; if ( allLocalKeys == true && allInheritedKeys == true ) { retval = null; } else if ( allInheritedKeys == true ) { retval = Boolean.TRUE; } else if ( allLocalKeys == true ) { retval = Boolean.FALSE; } else { retval = null; } if ( retval == null ) { inheritValues[ rowIndex ] = StyleDataBackend.NULL_INDICATOR; } else { inheritValues[ rowIndex ] = retval; } return retval; } private boolean defineExpressionValue( final StyleMetaData metaData, final Expression value ) { boolean changed = false; final Element[] elements = getDataBackend().getData(); for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[ i ]; final Expression attribute = element.getStyleExpression( metaData.getStyleKey() ); if ( ( ObjectUtilities.equal( attribute, value ) ) == false ) { changed = true; } } if ( changed ) { final ReportDocumentContext reportRenderContext = getReportRenderContext(); if ( reportRenderContext == null ) { throw new IllegalStateException( "No report render context? Thats bad." ); } final UndoManager undo = reportRenderContext.getUndo(); final ArrayList<UndoEntry> undos = new ArrayList<UndoEntry>(); for ( int i = 0; i < elements.length; i++ ) { final Element element = elements[ i ]; final Expression attribute = element.getStyleExpression( metaData.getStyleKey() ); if ( value == null ) { undos.add( new StyleExpressionEditUndoEntry ( element.getObjectID(), metaData.getStyleKey(), attribute, null ) ); element.setStyleExpression( metaData.getStyleKey(), null ); element.notifyNodePropertiesChanged(); } else { final Expression expression = value.getInstance(); undos.add( new StyleExpressionEditUndoEntry ( element.getObjectID(), metaData.getStyleKey(), attribute, expression ) ); element.setStyleExpression( metaData.getStyleKey(), expression ); element.notifyNodePropertiesChanged(); } } undo.addChange( Messages.getString( "StyleChange" ), new CompoundUndoEntry( (UndoEntry[]) undos.toArray( new UndoEntry[ undos.size() ] ) ) ); } return changed; } private Expression computeExpressionValue( final StyleMetaData metaData, final int row ) { final DefaultStyleDataBackend dataBackend1 = getDataBackend(); final Object[] expressionValues = dataBackend1.getExpressionValues(); final Object o = expressionValues[ row ]; if ( o == NULL_INDICATOR ) { return null; } if ( o != null ) { return (Expression) o; } Expression lastElement = null; final Element[] elements = dataBackend1.getData(); if ( elements.length > 0 ) { final Element element = elements[ 0 ]; lastElement = element.getStyleExpression( metaData.getStyleKey() ); } if ( lastElement != null ) { expressionValues[ row ] = lastElement; } else { expressionValues[ row ] = NULL_INDICATOR; } return lastElement; } public Class getClassForCell( final int rowIndex, final int columnIndex ) { final StyleMetaData metaData = getMetaData( rowIndex ); if ( metaData == null ) { return GroupingHeader.class; } switch( columnIndex ) { case 0: return GroupedName.class; case 1: return Boolean.class; case 2: return metaData.getTargetType(); case 3: return Expression.class; default: throw new IndexOutOfBoundsException(); } } public PropertyEditor getEditorForCell( final int rowIndex, final int columnIndex ) { final StyleMetaData metaData = getMetaData( rowIndex ); if ( metaData == null ) { return null; } switch( columnIndex ) { case 0: return null; case 1: return null; case 2: return computeEditor( metaData, rowIndex ); case 3: return null; default: throw new IndexOutOfBoundsException(); } } public ReportDocumentContext getReportRenderContext() { return reportRenderContext; } public void setReportRenderContext( final ReportDocumentContext reportRenderContext ) { this.reportRenderContext = reportRenderContext; } }