/*
* 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) 2009 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.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.designer.core.Messages;
import org.pentaho.reporting.designer.core.editor.ReportRenderContext;
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.GroupingModel;
import org.pentaho.reporting.designer.core.util.undo.AttributeEditUndoEntry;
import org.pentaho.reporting.designer.core.util.undo.AttributeExpressionEditUndoEntry;
import org.pentaho.reporting.designer.core.util.undo.CompoundUndoEntry;
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.ReportElement;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.engine.classic.core.metadata.AttributeMetaData;
import org.pentaho.reporting.engine.classic.core.metadata.ElementType;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
/**
* Todo: Document me!
*
* @author Thomas Morgner
*/
public class VisualAttributeTableModel extends AbstractAttributeTableModel implements GroupingModel
{
private static final Log logger = LogFactory.getLog(VisualAttributeTableModel.class);
protected static final Object[] EMPTY_VALUES = new Object[0];
protected static final Object NULL_INDICATOR = new Object();
protected static final Element[] EMPTY_ELEMENTS = new Element[0];
protected static final ElementType[] EMPTY_ELEMENT_TYPES = new ElementType[0];
private static final String[] EMPTY_FIELDS = new String[0];
private static class AttributeDataBackend extends DataBackend
{
private Element[] elements;
private Object[] fullValues;
private Object[] propertyEditors;
private Object[] expressionValues;
private ElementType[] elementTypes;
private AttributeDataBackend()
{
elements = EMPTY_ELEMENTS;
propertyEditors = EMPTY_VALUES;
fullValues = EMPTY_VALUES;
expressionValues = EMPTY_VALUES;
elementTypes = EMPTY_ELEMENT_TYPES;
}
private AttributeDataBackend(final AttributeMetaData[] metaData,
final GroupingHeader[] groupings,
final Element[] elements,
final ElementType[] elementTypes)
{
super(metaData, groupings);
this.elements = elements;
this.fullValues = new Object[metaData.length];
this.propertyEditors = new Object[metaData.length];
this.expressionValues = new Object[metaData.length];
this.elementTypes = elementTypes;
}
public Object[] getExpressionValues()
{
return expressionValues;
}
public Element[] getData()
{
return elements.clone();
}
public Object[] getFullValues()
{
return fullValues;
}
public void resetCache()
{
Arrays.fill(fullValues, null);
Arrays.fill(expressionValues, null);
}
public Object[] getPropertyEditors()
{
return propertyEditors;
}
public ElementType[] getElementTypes()
{
return elementTypes;
}
}
private ExecutorService pool;
public VisualAttributeTableModel()
{
setDataBackend(new AttributeDataBackend());
pool = Executors.newSingleThreadExecutor();
}
public void setData(final Element[] elements)
{
if (isSameElements(elements, getData(), getElementTypes()))
{
pool.submit(new SameElementsUpdateDataTask(getDataBackend()));
return;
}
pool.submit(new UpdateDataTask(elements));
}
protected DataBackend createDataBackend(final GroupingHeader[] headers,
final AttributeMetaData[] metaData,
final ReportElement[] elements,
final ElementType[] elementTypes)
{
super.createDataBackend(headers, metaData, elements, elementTypes);
return new AttributeDataBackend(metaData, headers, (Element[]) elements, elementTypes);
}
protected void refreshData()
{
pool.submit(new UpdateDataTask(getAttributeDataBackend().getData()));
}
protected AttributeDataBackend getAttributeDataBackend()
{
return (AttributeDataBackend) getDataBackend();
}
public Element[] getData()
{
return getAttributeDataBackend().getData();
}
public ElementType[] getElementTypes()
{
return getAttributeDataBackend().getElementTypes();
}
public int getColumnCount()
{
return 3;
}
public String getColumnName(final int column)
{
switch (column)
{
case 0:
return Messages.getString("VisualAttributeTableModel.NameColumn");
case 1:
return Messages.getString("VisualAttributeTableModel.ValueColumn");
case 2:
return Messages.getString("VisualAttributeTableModel.FormulaColumn");
default:
throw new IllegalArgumentException();
}
}
public Object getValueAt(final int rowIndex, final int columnIndex)
{
final AttributeMetaData metaData = getMetaData(rowIndex);
if (metaData == null)
{
return getGroupings(rowIndex);
}
switch (columnIndex)
{
case 0:
return new GroupedName(metaData.getDisplayName(Locale.getDefault()), metaData.getGrouping(Locale.getDefault()));
case 1:
return computeFullValue(metaData, rowIndex);
case 2:
return computeExpressionValue(metaData, rowIndex);
default:
throw new IndexOutOfBoundsException();
}
}
public boolean isCellEditable(final int rowIndex, final int columnIndex)
{
final AttributeMetaData metaData = getMetaData(rowIndex);
if (metaData == null)
{
return false;
}
switch (columnIndex)
{
case 0:
return false;
case 1:
return "ElementType".equals(metaData.getValueRole()) == false; // $NON-NLS$
case 2:
{
if ("ElementType".equals(metaData.getValueRole()))// $NON-NLS$
{
return false;
}
if (metaData.isDesignTimeValue())
{
return false;
}
return true;
}
default:
throw new IndexOutOfBoundsException();
}
}
public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex)
{
final AttributeMetaData metaData = getMetaData(rowIndex);
if (metaData == null)
{
return;
}
switch (columnIndex)
{
case 0:
return;
case 1:
{
if (defineFullValue(metaData, aValue))
{
final AttributeDataBackend db = (AttributeDataBackend) getDataBackend();
db.fullValues[rowIndex] = null;
fireTableDataChanged();
}
break;
}
case 2:
{
if (aValue != null && aValue instanceof Expression == false)
{
return;
}
if (defineExpressionValue(metaData, (Expression) aValue))
{
final AttributeDataBackend db = (AttributeDataBackend) getDataBackend();
db.expressionValues[rowIndex] = null;
fireTableDataChanged();
}
break;
}
default:
throw new IndexOutOfBoundsException();
}
}
private boolean defineFullValue(final AttributeMetaData metaData,
final Object value)
{
if (value != null && metaData.getTargetType().isInstance(value) == false)
{
// not the correct type
logger.warn("Invalid type: " + value + " but expected " + metaData.getTargetType());// $NON-NLS$
return false;
}
final ReportRenderContext reportRenderContext = getReportRenderContext();
if (reportRenderContext == null)
{
throw new IllegalStateException("No report render context? Thats bad.");
}
final UndoManager undo = reportRenderContext.getUndo();
boolean changed = false;
final ReportElement[] elements = getAttributeDataBackend().getData();
final ArrayList<UndoEntry> undos = new ArrayList<UndoEntry>();
for (int i = 0; i < elements.length; i++)
{
final ReportElement element = elements[i];
final Object attribute = element.getAttribute(metaData.getNameSpace(), metaData.getName());
if ((ObjectUtilities.equal(attribute, value)) == false)
{
undos.add(new AttributeEditUndoEntry
(element.getObjectID(), metaData.getNameSpace(), metaData.getName(), attribute, value));
element.setAttribute(metaData.getNameSpace(), metaData.getName(), value);
changed = true;
}
}
undo.addChange(Messages.getString("VisualAttributeTableModel.UndoName"),
new CompoundUndoEntry((UndoEntry[]) undos.toArray(new UndoEntry[undos.size()])));
return changed;
}
private Object computeFullValue(final AttributeMetaData metaData, final int row)
{
final AttributeDataBackend dataBackend = getAttributeDataBackend();
final Object[] fullValues = dataBackend.getFullValues();
final Object o = fullValues[row];
if (o == NULL_INDICATOR)
{
return null;
}
if (o != null)
{
return o;
}
Object lastElement = null;
final ReportElement[] elements = dataBackend.getData();
if (elements.length > 0)
{
final ReportElement element = elements[0];
lastElement = element.getAttribute(metaData.getNameSpace(), metaData.getName());
}
if (lastElement != null)
{
fullValues[row] = lastElement;
}
else
{
fullValues[row] = NULL_INDICATOR;
}
return lastElement;
}
private boolean defineExpressionValue(final AttributeMetaData metaData,
final Expression value)
{
boolean changed = false;
final Element[] elements = getAttributeDataBackend().getData();
for (int i = 0; i < elements.length; i++)
{
final Element element = elements[i];
final Expression attribute = element.getAttributeExpression
(metaData.getNameSpace(), metaData.getName());
if ((ObjectUtilities.equal(attribute, value)) == false)
{
changed = true;
}
}
if (changed)
{
final ReportRenderContext 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.getAttributeExpression
(metaData.getNameSpace(), metaData.getName());
if (value != null)
{
final Expression expression = value.getInstance();
undos.add(new AttributeExpressionEditUndoEntry
(element.getObjectID(), metaData.getNameSpace(), metaData.getName(), attribute, expression));
element.setAttributeExpression(metaData.getNameSpace(), metaData.getName(), expression);
}
else
{
undos.add(new AttributeExpressionEditUndoEntry
(element.getObjectID(), metaData.getNameSpace(), metaData.getName(), attribute, null));
element.setAttributeExpression(metaData.getNameSpace(), metaData.getName(), null);
}
}
undo.addChange(Messages.getString("VisualAttributeTableModel.UndoNameExpression"),
new CompoundUndoEntry((UndoEntry[]) undos.toArray(new UndoEntry[undos.size()])));
}
return changed;
}
private Expression computeExpressionValue(final AttributeMetaData metaData,
final int row)
{
final AttributeDataBackend dataBackend1 = getAttributeDataBackend();
final Object[] expressionValues = dataBackend1.getExpressionValues();
final Object o = expressionValues[row];
if (o == NULL_INDICATOR)
{
return null;
}
if (o != null)
{
return (Expression) o;
}
if (metaData.isDesignTimeValue())
{
expressionValues[row] = NULL_INDICATOR;
return null;
}
Expression lastElement = null;
final Element[] elements = dataBackend1.getData();
if (elements.length > 0)
{
final Element element = elements[0];
lastElement = element.getAttributeExpression(metaData.getNameSpace(), metaData.getName());
}
if (lastElement != null)
{
expressionValues[row] = lastElement;
}
else
{
expressionValues[row] = NULL_INDICATOR;
}
return lastElement;
}
public Class getClassForCell(final int rowIndex, final int columnIndex)
{
final AttributeMetaData metaData = getMetaData(rowIndex);
if (metaData == null)
{
return GroupingHeader.class;
}
switch (columnIndex)
{
case 0:
return GroupedName.class;
case 1:
return metaData.getTargetType();
case 2:
if (metaData.isDesignTimeValue())
{
// disables the expression-editor.
return Object.class;
}
return Expression.class;
default:
throw new IndexOutOfBoundsException();
}
}
public PropertyEditor getEditorForCell(final int rowIndex, final int columnIndex)
{
final AttributeMetaData metaData = getMetaData(rowIndex);
if (metaData == null)
{
return null;
}
switch (columnIndex)
{
case 0:
return null;
case 1:
return computeEditor(metaData, rowIndex);
case 2:
return null;
default:
throw new IndexOutOfBoundsException();
}
}
private PropertyEditor computeEditor(final AttributeMetaData metaData, final int row)
{
final Object[] propertyEditors = getAttributeDataBackend().getPropertyEditors();
final Object o = propertyEditors[row];
if (o == NULL_INDICATOR)
{
return null;
}
if (o != null)
{
return (PropertyEditor) o;
}
PropertyEditor propertyEditor = metaData.getEditor();
if (propertyEditor == null)
{
propertyEditor = getDefaultEditor(metaData.getTargetType(), metaData.getValueRole());
}
if (propertyEditor == null)
{
propertyEditors[row] = NULL_INDICATOR;
}
else
{
propertyEditors[row] = propertyEditor;
}
return propertyEditor;
}
public String getValueRole(final int row, final int column)
{
if (column != 1)
{
return null;
}
final AttributeMetaData metaData = getMetaData(row);
if (metaData == null)
{
return null;
}
return metaData.getValueRole();
}
public String[] getExtraFields(final int row, final int column)
{
if (column == 0)
{
return EMPTY_FIELDS;
}
final AttributeMetaData metaData = getMetaData(row);
if (metaData == null)
{
return EMPTY_FIELDS;
}
return metaData.getExtraCalculationFields();
}
public GroupingHeader getGroupHeader(final int index)
{
return getGroupings(index);
}
public boolean isHeaderRow(final int index)
{
return getDataBackend().getMetaData(index) == null;
}
}