/*!
* 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 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;
import javax.swing.SwingUtilities;
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;
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().isShowExpertItems() == false && data.isExpert())
{
continue;
}
if (WorkspaceSettings.getInstance().isShowDeprecatedItems() == false && data.isDeprecated())
{
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().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);
//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().isShowExpertItems() == false && data.isExpert())
{
attributes.put(name, Boolean.FALSE);
continue;
}
if (WorkspaceSettings.getInstance().isShowDeprecatedItems() == false && data.isDeprecated())
{
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;
}
}