/*!
* 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.attributes;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.settings.WorkspaceSettings;
import org.pentaho.reporting.designer.core.util.FastPropertyEditorManager;
import org.pentaho.reporting.designer.core.util.exceptions.UncaughtExceptionsModel;
import org.pentaho.reporting.designer.core.util.table.ElementMetaDataTableModel;
import org.pentaho.reporting.designer.core.util.table.GroupingHeader;
import org.pentaho.reporting.designer.core.util.table.ResourcePropertyEditor;
import org.pentaho.reporting.designer.core.util.table.TableStyle;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.ReportElement;
import org.pentaho.reporting.engine.classic.core.metadata.AttributeMetaData;
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.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.xmlns.common.AttributeMap;
import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.beans.PropertyEditor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
/**
* Todo: Document me!
*
* @author Thomas Morgner
*/
public abstract class AbstractAttributeTableModel
extends AbstractTableModel implements ElementMetaDataTableModel
{
protected static final GroupingHeader[] EMPTY_GROUPINGS = new GroupingHeader[ 0 ];
protected static final AttributeMetaData[] EMPTY_METADATA = new AttributeMetaData[ 0 ];
protected class NotifyChangeTask implements Runnable {
private DataBackend dataBackend;
protected NotifyChangeTask( final DataBackend dataBackend ) {
this.dataBackend = dataBackend;
}
public void run() {
setDataBackend( dataBackend );
fireTableDataChanged();
}
}
protected class SameElementsUpdateDataTask implements Runnable {
private DataBackend dataBackend;
protected SameElementsUpdateDataTask( final DataBackend elements ) {
this.dataBackend = elements;
}
public void run() {
dataBackend.resetCache();
try {
if ( SwingUtilities.isEventDispatchThread() ) {
setDataBackend( dataBackend );
fireTableDataChanged();
} else {
SwingUtilities.invokeAndWait( new NotifyChangeTask( dataBackend ) );
}
} catch ( Exception e ) {
UncaughtExceptionsModel.getInstance().addException( e );
}
}
}
protected class UpdateDataTask implements Runnable {
private ReportElement[] elements;
protected UpdateDataTask( final ReportElement[] elements ) {
this.elements = elements.clone();
}
public void run() {
try {
final DataBackend dataBackend = updateData( elements );
if ( SwingUtilities.isEventDispatchThread() ) {
setDataBackend( dataBackend );
fireTableDataChanged();
} else {
SwingUtilities.invokeAndWait( new NotifyChangeTask( dataBackend ) );
}
} catch ( Exception e ) {
UncaughtExceptionsModel.getInstance().addException( e );
}
}
}
protected abstract static class DataBackend {
private AttributeMetaData[] metaData;
private GroupingHeader[] groupings;
public DataBackend() {
groupings = EMPTY_GROUPINGS;
metaData = EMPTY_METADATA;
}
public abstract void resetCache();
public DataBackend( final AttributeMetaData[] metaData, final GroupingHeader[] groupings ) {
this.metaData = metaData;
this.groupings = groupings;
}
public int getRowCount() {
return metaData.length;
}
protected AttributeMetaData getMetaData( final int row ) {
//noinspection ReturnOfCollectionOrArrayField, as this is for internal use only
return metaData[ row ];
}
protected GroupingHeader getGroupings( final int row ) {
//noinspection ReturnOfCollectionOrArrayField, as this is for internal use only
return groupings[ row ];
}
protected GroupingHeader[] getGroupings() {
return groupings;
}
}
private DataBackend dataBackend, oldDataBackend;
private TableStyle tableStyle;
private ReportDocumentContext reportRenderContext;
protected AbstractAttributeTableModel() {
tableStyle = TableStyle.GROUPED;
}
public int getRowCount() {
return dataBackend.getRowCount();
}
protected AttributeMetaData getMetaData( final int row ) {
return dataBackend.getMetaData( row );
}
protected GroupingHeader getGroupings( final int row ) {
return dataBackend.getGroupings( row );
}
public TableStyle getTableStyle() {
return tableStyle;
}
public void setTableStyle( final TableStyle tableStyle ) {
if ( tableStyle == null ) {
throw new NullPointerException();
}
this.tableStyle = tableStyle;
refreshData();
}
protected abstract void refreshData();
protected static boolean isSameElements( final ReportElement[] elements,
final ReportElement[] existingElements,
final ElementType[] elementTypes ) {
/*
if (elements == existingElements)
{
return true;
}
*/
if ( elements.length != existingElements.length ) {
// that is easy!
return false;
}
for ( int i = 0; i < elements.length; i++ ) {
final Element element = (Element) elements[ i ];
if ( existingElements[ i ].getObjectID() != element.getObjectID() ) {
return false;
}
if ( elementTypes != null ) {
if ( !element.getElementType().getClass().equals( elementTypes[ i ].getClass() ) ) {
return false;
}
}
}
return true;
}
public synchronized DataBackend getDataBackend() {
return dataBackend;
}
public synchronized void setDataBackend( final DataBackend dataBackend ) {
this.dataBackend = dataBackend;
}
/**
* @param headers
* @param metaData
* @param elements
* @return null - Concrete implementations MUST override this method and call super.createDataBackend(headers,
* metaData, elements) BEFORE any other code is executed. Then they must return a implementation of Databackend
*/
protected DataBackend createDataBackend( final GroupingHeader[] headers,
final AttributeMetaData[] metaData,
final ReportElement[] elements,
final ElementType[] elementTypes ) {
oldDataBackend = this.getDataBackend();
return null;
}
protected DataBackend updateData( final ReportElement[] elements ) {
final AttributeMetaData[] metaData = selectCommonAttributes( elements );
final ArrayList<ElementType> elementTypesArray = new ArrayList<ElementType>();
for ( int i = 0; i < elements.length; i++ ) {
final Element element = (Element) elements[ i ];
elementTypesArray.add( element.getElementType() );
}
final ElementType[] elementTypes = elementTypesArray.toArray( new ElementType[ elementTypesArray.size() ] );
if ( tableStyle == TableStyle.ASCENDING ) {
Arrays.sort( metaData, new PlainMetaDataComparator() );
return ( createDataBackend( new GroupingHeader[ metaData.length ], metaData, elements, elementTypes ) );
} else if ( tableStyle == TableStyle.DESCENDING ) {
Arrays.sort( metaData, Collections.reverseOrder( new PlainMetaDataComparator() ) );
return ( createDataBackend( new GroupingHeader[ metaData.length ], metaData, elements, elementTypes ) );
} else {
GroupingHeader[] groupings;
Arrays.sort( metaData, new GroupedMetaDataComparator() );
int groupCount = 0;
int metaDataCount = 0;
final Locale locale = Locale.getDefault();
if ( metaData.length > 0 ) {
String oldValue = null;
for ( int i = 0; i < metaData.length; i++ ) {
final AttributeMetaData data = metaData[ i ];
if ( data.isHidden() ) {
continue;
}
if ( !WorkspaceSettings.getInstance().isVisible( data ) ) {
continue;
}
metaDataCount += 1;
if ( groupCount == 0 ) {
groupCount = 1;
final AttributeMetaData 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 AttributeMetaData[] groupedMetaData = new AttributeMetaData[ metaDataCount + groupCount ];
int targetIdx = 0;
groupings = new GroupingHeader[ groupedMetaData.length ];
GroupingHeader group = null;
for ( int sourceIdx = 0; sourceIdx < metaData.length; sourceIdx++ ) {
final AttributeMetaData 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 );
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 ( createDataBackend( groupings, groupedMetaData, elements, elementTypes ) );
}
}
/**
* Uses the name of the old groupings to set the collapse status of the new groupings so that when a user makes a
* selection not all of the groups return to the expanded state. In essence makes group collapses "sticky" where the
* group heading hasn't changed.
*
* @param groupings
* @param oldGroupings
*/
private GroupingHeader[] reconcileState( final GroupingHeader[] groupings, final GroupingHeader[] oldGroupings ) {
for ( final GroupingHeader header : groupings ) {
final GroupingHeader oldHeader = findFirstOccuranceOfHeaderTitle( oldGroupings, header.getHeaderText() );
if ( oldHeader != null ) {
header.setCollapsed( oldHeader.isCollapsed() );
}
}
return groupings;
}
private GroupingHeader findFirstOccuranceOfHeaderTitle( final GroupingHeader[] headerArray,
final String headerTitle ) {
for ( final GroupingHeader header : headerArray ) {
if ( header == null ) {
continue;
}
if ( ObjectUtilities.equal( header.getHeaderText(), headerTitle ) ) {
return header;
}
}
return null;
}
private static AttributeMetaData[] selectCommonAttributes( final ReportElement[] elements ) {
final AttributeMap<Object> attributes = new AttributeMap<Object>();
final ArrayList<AttributeMetaData> selectedArrays = new ArrayList<AttributeMetaData>();
for ( int elementCount = 0; elementCount < elements.length; elementCount++ ) {
final ReportElement element = elements[ elementCount ];
final AttributeMetaData[] datas = element.getMetaData().getAttributeDescriptions();
for ( int j = 0; j < datas.length; j++ ) {
final AttributeMetaData data = datas[ j ];
final String name = data.getName();
final String namespace = data.getNameSpace();
if ( data.isHidden() ) {
attributes.setAttribute( namespace, name, Boolean.FALSE );
continue;
}
if ( !WorkspaceSettings.getInstance().isVisible( data ) ) {
continue;
}
final Object attribute = attributes.getAttribute( namespace, name );
if ( Boolean.TRUE.equals( attribute ) ) {
// fine, we already have a value for it.
} else if ( attribute == null ) {
// add it ..
if ( elementCount == 0 ) {
attributes.setAttribute( namespace, name, Boolean.TRUE );
} else {
attributes.setAttribute( namespace, name, Boolean.FALSE );
}
}
}
}
final String[] namespaces = attributes.getNameSpaces();
for ( int nsIdx = 0; nsIdx < namespaces.length; nsIdx++ ) {
final String namespace = namespaces[ nsIdx ];
final String[] names = attributes.getNames( namespace );
for ( int namesIdx = 0; namesIdx < names.length; namesIdx++ ) {
final String name = names[ namesIdx ];
final Object attribute = attributes.getAttribute( namespace, name );
if ( Boolean.TRUE.equals( attribute ) ) {
selectedArrays.add( find( elements[ 0 ].getMetaData().getAttributeDescriptions(), namespace, name ) );
}
}
}
return selectedArrays.toArray( new AttributeMetaData[ selectedArrays.size() ] );
}
private static AttributeMetaData find( final AttributeMetaData[] data, final String namespace, final String name ) {
for ( int i = 0; i < data.length; i++ ) {
final AttributeMetaData attributeMetaData = data[ i ];
if ( attributeMetaData.getName().equals( name ) && attributeMetaData.getNameSpace().equals( namespace ) ) {
return attributeMetaData;
}
}
return null;
}
protected PropertyEditor getDefaultEditor( final Class type, final String valueRole ) {
if ( String.class.equals( type ) ) {
return null;
}
if ( AttributeMetaData.VALUEROLE_RESOURCE.equals( valueRole ) ) {
return new ResourcePropertyEditor( reportRenderContext );
}
return FastPropertyEditorManager.findEditor( type );
}
public ReportDocumentContext getReportRenderContext() {
return reportRenderContext;
}
public void setReportRenderContext( final ReportDocumentContext reportRenderContext ) {
this.reportRenderContext = reportRenderContext;
}
}