/*!
* 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 java.beans.PropertyEditor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
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;
/**
* 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().isShowExpertItems() == false && data.isExpert())
{
continue;
}
if (WorkspaceSettings.getInstance().isShowDeprecatedItems() == false && data.isDeprecated())
{
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().isShowExpertItems() == false && data.isExpert())
{
continue;
}
if (WorkspaceSettings.getInstance().isShowDeprecatedItems() == false && data.isDeprecated())
{
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().isShowExpertItems() == false && data.isExpert())
{
attributes.setAttribute(namespace, name, Boolean.FALSE);
continue;
}
if (WorkspaceSettings.getInstance().isShowDeprecatedItems() == false && data.isDeprecated())
{
attributes.setAttribute(namespace, name, Boolean.FALSE);
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;
}
}