/*
* Copyright (c) Thomas Parker, 2009.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 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 Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package pcgen.cdom.facet.base;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import pcgen.cdom.base.Category;
import pcgen.cdom.base.PCGenIdentifier;
import pcgen.cdom.enumeration.Nature;
import pcgen.cdom.facet.CategorizedDataFacetChangeEvent;
import pcgen.cdom.facet.event.DataFacetChangeEvent;
import pcgen.cdom.facet.event.DataFacetChangeListener;
/**
* A AbstractDataFacet is a DataFacet that contains information about
* CDOMObjects that are contained in a resource. This serves the basic functions
* of managing the DataFacetChangeListeners for a DataFacet.
*
* Note that DataFacetChangeListeners registered with the AbstractDataFacet
* through the methods in AbstractDataFacet will receive events from the
* AbstractDataFacet in the order of the priority given during their
* registration. DataFacetChangeListeners with a lower priority (starting with
* Integer.MIN_VALUE) will receive events first. All DataFacetChangeListeners at
* a given priority will receive events before DataFacetChangeListeners at a
* higher priority.
*
* Note also that AbstractDataFacet makes no guarantees as to the order in which
* DataFacetChangeListners of the <b>same</b> priority will receive events from
* the AbstractDataFacet.
*
* @param <IDT>
* The Type of identifier used in this AbstractDataFacet
* @param <T>
* The Type of object stored in this AbstractDataFacet
*/
public abstract class AbstractDataFacet<IDT extends PCGenIdentifier, T> extends
AbstractStorageFacet<IDT>
{
private final Map<Integer, DataFacetChangeListener<IDT, ? super T>[]> listeners =
new TreeMap<>();
/**
* Adds a new DataFacetChangeListener to receive DataFacetChangeEvents
* (EdgeChangeEvent and NodeChangeEvent) from the source DataFacet. The
* given DataFacetChangeListener is added at the default priority (zero).
*
* Note that the DataFacetChangeListeners are a list, meaning a given
* DataFacetChangeListener can be added more than once at a given priority,
* and if that occurs, it must be removed an equivalent number of times in
* order to no longer receive events from this AbstractDataFacet.
*
* @param listener
* The DataFacetChangeListener to receive DataFacetChangeEvents
* from this AbstractDataFacet
*/
public void addDataFacetChangeListener(
DataFacetChangeListener<IDT, ? super T> listener)
{
addDataFacetChangeListener(0, listener);
}
/**
* Adds a new DataFacetChangeListener to receive DataFacetChangeEvents
* (EdgeChangeEvent and NodeChangeEvent) from the source DataFacet.
*
* The DataFacetChangeListener is added at the given priority.
*
* Note that the DataFacetChangeListeners are a list, meaning a given
* DataFacetChangeListener can be added more than once at a given priority,
* and if that occurs, it must be removed an equivalent number of times in
* order to no longer receive events from this AbstractDataFacet.
*
* @param priority
* The lower the priority the earlier in the list the new
* listener will get advised of the change.
* @param listener
* The DataFacetChangeListener to receive DataFacetChangeEvents
* from this AbstractDataFacet
*/
public void addDataFacetChangeListener(int priority,
DataFacetChangeListener<IDT, ? super T> listener)
{
DataFacetChangeListener<IDT, ? super T>[] dfcl =
listeners.get(priority);
int newSize = (dfcl == null) ? 1 : (dfcl.length + 1);
DataFacetChangeListener<IDT, ? super T>[] newArray =
new DataFacetChangeListener[newSize];
if (dfcl != null)
{
System.arraycopy(dfcl, 0, newArray, 1, dfcl.length);
}
newArray[0] = listener;
listeners.put(priority, newArray);
}
/**
* Removes a DataFacetChangeListener so that it will no longer receive
* DataFacetChangeEvents from the source DataFacet. This will remove the
* data facet change listener from the default priority (zero).
*
* Note that if the given DataFacetChangeListener has been registered under
* a different priority, it will still receive events at that priority
* level.
*
* @param listener
* The DataFacetChangeListener to be removed
*/
public void removeDataFacetChangeListener(
DataFacetChangeListener<IDT, ? super T> listener)
{
removeDataFacetChangeListener(0, listener);
}
/**
* Removes a DataFacetChangeListener so that it will no longer receive
* DataFacetChangeEvents from the source DataFacet. This will remove the
* data facet change listener from the given priority.
*
* Note that if the given DataFacetChangeListener has been registered under
* a different priority, it will still receive events at that priority
* level.
*
* @param listener
* The DataFacetChangeListener to be removed
*/
public void removeDataFacetChangeListener(int priority,
DataFacetChangeListener<IDT, ? super T> listener)
{
DataFacetChangeListener<IDT, ? super T>[] dfcl =
listeners.get(priority);
if (dfcl == null)
{
// No worries
return;
}
int foundLoc = -1;
int newSize = dfcl.length - 1;
for (int i = newSize; i >= 0; i--)
{
if (dfcl[i] == listener)
{
foundLoc = i;
break;
}
}
if (foundLoc != -1)
{
if (dfcl.length == 1)
{
listeners.remove(priority);
}
else
{
DataFacetChangeListener<IDT, ? super T>[] newArray =
new DataFacetChangeListener[newSize];
if (foundLoc != 0)
{
System.arraycopy(dfcl, 0, newArray, 0, foundLoc);
}
if (foundLoc != newSize)
{
System.arraycopy(dfcl, foundLoc + 1, newArray, foundLoc,
newSize - foundLoc);
}
listeners.put(priority, newArray);
}
}
}
/**
* Sends a NodeChangeEvent to the DataFacetChangeListeners that are
* receiving DataFacetChangeEvents from the source DataFacet.
*
* @param id
* The PCGenIdentifier identifying the resource to which the
* NodeChangeEvent relates.
* @param node
* The Node that has been added to or removed from the source
* DataFacet for the given PCGenIdentifier
* @param type
* An identifier indicating whether the given CDOMObject was
* added to or removed from the source DataFacet
*/
protected void fireDataFacetChangeEvent(IDT id, T node, int type)
{
fireDataFacetChangeEvent(id, node, type, null, null);
}
/**
* Sends a NodeChangeEvent to the DataFacetChangeListeners that are
* receiving DataFacetChangeEvents from the source DataFacet.
*
* @param id
* The PCGenIdentifier identifying the resource to which the
* NodeChangeEvent relates.
* @param node
* The Node that has been added to or removed from the source
* DataFacet for the given PCGenIdentifier
* @param type
* An identifier indicating whether the given CDOMObject was
* added to or removed from the source DataFacet
* @param category
* The category (e.g. AbilityCategory) in which the node has been
* changed.
* @param nature
* The optional nature in which the node has been changed.
*/
@SuppressWarnings("rawtypes")
protected void fireDataFacetChangeEvent(IDT id, T node, int type,
Category category, Nature nature)
{
for (DataFacetChangeListener<IDT, ? super T>[] dfclArray : listeners
.values())
{
/*
* This list is decremented from the end of the list to the
* beginning in order to maintain consistent operation with how Java
* AWT and Swing listeners are notified of Events (they are in
* reverse order to how they were added to the Event-owning object).
* This is obviously subordinate to the priority (loop above).
*/
DataFacetChangeEvent<IDT, T> ccEvent = null;
for (int i = dfclArray.length - 1; i >= 0; i--)
{
// Lazily create event
if (ccEvent == null)
{
if (category == null)
{
ccEvent =
new DataFacetChangeEvent<>(id, node,
this, type);
}
else
{
ccEvent =
new CategorizedDataFacetChangeEvent<>(id,
node, this, type, category, nature);
}
}
DataFacetChangeListener dfcl = dfclArray[i];
switch (ccEvent.getEventType())
{
case DataFacetChangeEvent.DATA_ADDED:
dfcl.dataAdded(ccEvent);
break;
case DataFacetChangeEvent.DATA_REMOVED:
dfcl.dataRemoved(ccEvent);
break;
default:
break;
}
}
}
}
public DataFacetChangeListener<IDT, ? super T>[] getDataFacetChangeListeners()
{
List<DataFacetChangeListener<IDT, ? super T>> list =
new ArrayList<>();
for (DataFacetChangeListener<IDT, ? super T>[] dfclArray : listeners
.values())
{
Collections.addAll(list, dfclArray);
}
return list.toArray(new DataFacetChangeListener[list.size()]);
}
}