/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util.model;
import javax.swing.DefaultListModel;
import com.servoy.j2db.util.Debug;
/**
* A DefaultListModel capable of firing events bundled. For example if you expect to add more elements to the list model, then you tell this to the list model
* by calling startBundlingEvents(), add as many elements as you like in a continuous interval, then call stopBundlingEvents() and only one intervalAdded event
* will be fired to listeners.<br>
* <br>
* <b>If startBundlingEvents() was called, and operations of different types/in different continuous intervals are performed, you will get an
* IllegalStateException, because such operations cannot be bundled, so be careful when using startBundlingEvents() and stopBundlingEvents().</b><br>
* <br>
* This could be implemented to generate cached events instead of throwing IllegalStateException for these cases above, but then operations need to be
* intercepted before the underlying vector is changed.
*
* @author acostescu
*/
public class OptimizedDefaultListModel extends DefaultListModel
{
private final static int ADDED = 0;
private final static int REMOVED = 1;
private final static int CHANGED = 2;
private final static int NONE = -2;
private int bundledEventType = NONE;
private int bundledIndex1;
private int bundledIndex2;
private boolean bundleEvents = false;
public void startBundlingEvents()
{
if (bundleEvents && bundledEventType != NONE)
{
// in case startBundling is called twice, without a stopBundling, something is wrong, but try to handle it anyway (to avoid IllegalStateExceptions that might happen later)
fireBundledEvent();
Debug.warn("startBundlingEvents() called twice without stopBundlingEvents(). This is not expected and might hide unwanted behavior...");
}
bundleEvents = true;
}
public void stopBundlingEvents()
{
bundleEvents = false;
if (bundledEventType != NONE)
{
fireBundledEvent();
}
}
private void fireBundledEvent()
{
if (bundledEventType == ADDED)
{
fireIntervalAdded(this, bundledIndex1, bundledIndex2);
}
else if (bundledEventType == REMOVED)
{
fireIntervalRemoved(this, bundledIndex1, bundledIndex2);
}
else if (bundledEventType == CHANGED)
{
fireContentsChanged(this, bundledIndex1, bundledIndex2);
}
bundledEventType = NONE;
}
// intercept all
@Override
protected void fireContentsChanged(Object source, int index1, int index2)
{
if (bundleEvents)
{
if (bundledEventType == NONE)
{
bundledEventType = CHANGED;
bundledIndex1 = Math.min(index1, index2);
bundledIndex2 = Math.max(index1, index2);
}
else if (bundledEventType == CHANGED)
{
int i1 = Math.min(index1, index2);
int i2 = Math.max(index1, index2);
if ((bundledIndex2 < i1 - 1) || (bundledIndex1 > i2 + 1))
{
throw new IllegalStateException("Cannot bundle 'changed' event; intervals cannot be merged"); //$NON-NLS-1$
}
else
{
bundledIndex1 = Math.min(bundledIndex1, i1);
bundledIndex2 = Math.max(bundledIndex2, i2);
}
}
else
{
throw new IllegalStateException("Cannot bundle 'changed' event with '" + getEventTypeDescription(bundledEventType) + "' events"); //$NON-NLS-1$//$NON-NLS-2$
}
}
else
{
super.fireContentsChanged(source, index1, index2);
}
}
@Override
protected void fireIntervalAdded(Object source, int index1, int index2)
{
if (bundleEvents)
{
if (bundledEventType == NONE)
{
bundledEventType = ADDED;
bundledIndex1 = Math.min(index1, index2);
bundledIndex2 = Math.max(index1, index2);
}
else if (bundledEventType == ADDED)
{
int i1 = Math.min(index1, index2);
int i2 = Math.max(index1, index2);
if ((bundledIndex2 < i1 - 1) || (bundledIndex1 > i1 + 1))
{
throw new IllegalStateException("Cannot bundle 'added' event; intervals cannot be merged"); //$NON-NLS-1$
}
else
{
bundledIndex1 = Math.min(bundledIndex1, i1);
bundledIndex2 += i2 - i1 + 1;
}
}
else
{
throw new IllegalStateException("Cannot bundle 'added' event with '" + getEventTypeDescription(bundledEventType) + "' events"); //$NON-NLS-1$//$NON-NLS-2$
}
}
else
{
super.fireIntervalAdded(source, index1, index2);
}
}
@Override
protected void fireIntervalRemoved(Object source, int index1, int index2)
{
if (bundleEvents)
{
if (bundledEventType == NONE)
{
bundledEventType = REMOVED;
bundledIndex1 = Math.min(index1, index2);
bundledIndex2 = Math.max(index1, index2);
}
else if (bundledEventType == REMOVED)
{
int i1 = Math.min(index1, index2);
int i2 = Math.max(index1, index2);
if ((i1 > bundledIndex1) || (i2 < bundledIndex1 - 1))
{
throw new IllegalStateException("Cannot bundle 'removed' event; intervals cannot be merged"); //$NON-NLS-1$
}
else
{
bundledIndex2 += i2 - bundledIndex1 + 1;
bundledIndex1 = Math.min(bundledIndex1, i1);
}
}
else
{
throw new IllegalStateException("Cannot bundle 'removed' event with '" + getEventTypeDescription(bundledEventType) + "' events"); //$NON-NLS-1$//$NON-NLS-2$
}
}
else
{
super.fireIntervalRemoved(source, index1, index2);
}
}
private String getEventTypeDescription(int bundledEventType)
{
switch (bundledEventType)
{
case NONE :
return "none"; //$NON-NLS-1$
case ADDED :
return "added"; //$NON-NLS-1$
case REMOVED :
return "removed"; //$NON-NLS-1$
case CHANGED :
return "changed"; //$NON-NLS-1$
default :
return "unknown"; //$NON-NLS-1$
}
}
}