/*
* Copyright (c) Thomas Parker, 2012.
*
* 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.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import pcgen.base.util.ListSet;
import pcgen.cdom.base.PCGenIdentifier;
import pcgen.cdom.facet.event.DataFacetChangeEvent;
/**
* An AbstractAssociationFacet is a DataFacet that contains information about
* associations for Objects.
*
* This is used when each source may only have one association (such as
* associating hit points to a class level), although the target can be a class
* that can store multiple pieces of information in some implementations.
*
* If the source object (e.g. the class level) is re-added with a second
* association, this will overwrite the original association.
*
* null is NOT a valid source.
*
* null is NOT a valid base object or association.
*/
public abstract class AbstractAssociationFacet<IDT extends PCGenIdentifier, S, A>
extends AbstractScopeFacet<IDT, S, A>
{
/**
* Gets the association for the item identified by the given PCGenIdentifier
* and the given source object.
*
* @param id
* The PCGenIdentifier identifying the item for which the
* association get is being performed.
* @param obj
* The source object for which the association get is being
* performed.
* @return The association for the item (identified by the given
* PCGenIdentifier) and the given source object
*/
public A get(IDT id, S obj)
{
if (obj == null)
{
throw new IllegalArgumentException(
"Object for getting association may not be null");
}
Map<S, A> map = getCachedMap(id);
if (map != null)
{
return map.get(obj);
}
return null;
}
/**
* Set the given association for the given object in this
* AbstractAssociationFacet for the item represented by the given
* PCGenIdentifier
*
* @param id
* The PCGenIdentifier representing the item for which the given
* association should be made
* @param obj
* The object for which the association will be set
* @param association
* The association for the given object
*/
public void set(IDT id, S obj, A association)
{
if (obj == null)
{
throw new IllegalArgumentException("Object to add may not be null");
}
if (association == null)
{
throw new IllegalArgumentException("Association may not be null");
}
A old = getConstructingCachedMap(id).put(obj, association);
if (old != null)
{
fireScopeFacetChangeEvent(id, obj, old,
DataFacetChangeEvent.DATA_REMOVED);
}
fireScopeFacetChangeEvent(id, obj, association,
DataFacetChangeEvent.DATA_ADDED);
}
/**
* Removes the association for the given source object in this
* AbstractAssociationFacet for the item represented by the given
* PCGenIdentifier.
*
* @param id
* The PCGenIdentifier representing the item from which the given
* item association should be removed
* @param obj
* The object for which the association should be removed
*/
public void remove(IDT id, S obj)
{
Map<S, A> map = getCachedMap(id);
if (map != null)
{
A old = map.remove(obj);
if (old != null)
{
// Only send out notifications if we really removed something.
fireScopeFacetChangeEvent(id, obj, old,
DataFacetChangeEvent.DATA_REMOVED);
}
}
}
/**
* Removes all objects (and all associations for those objects) from the
* list of objects stored in this AbstractAssociationFacet for the Player
* Character represented by the given PCGenIdentifier
*
* This method is value-semantic in that ownership of the returned Map is
* transferred to the class calling this method. Since this is a remove all
* function, modification of the returned Map will not modify this
* AbstractAssociationFacet and modification of this
* AbstractAssociationFacet will not modify the returned Map. Modifications
* to the returned List will also not modify any future or previous objects
* returned by this (or other) methods on AbstractAssociationFacet. If you
* wish to modify the information stored in this AbstractAssociationFacet,
* you must use the add*() and remove*() methods of
* AbstractAssociationFacet.
*
* @param id
* The PCGenIdentifier representing the item from which all items
* should be removed
* @return A non-null Map of objects to their associations that were removed
* from this AbstractAssociationFacet for the item represented by
* the given PCGenIdentifier
*/
public Map<S, A> removeAll(IDT id)
{
Map<S, A> componentMap = (Map<S, A>) removeCache(id);
if (componentMap == null)
{
return Collections.emptyMap();
}
for (Map.Entry<S, A> entry : componentMap.entrySet())
{
fireScopeFacetChangeEvent(id, entry.getKey(), entry.getValue(),
DataFacetChangeEvent.DATA_REMOVED);
}
return componentMap;
}
/**
* Returns a non-null copy of the Set of objects in this
* AbstractAssociationFacet for the item represented by the given
* PCGenIdentifier. This method returns an empty set if no objects are in
* this AbstractAssociationFacet for the item identified by the given
* PCGenIdentifier.
*
* This method is value-semantic in that ownership of the returned List is
* transferred to the class calling this method. Modification of the
* returned List will not modify this AbstractAssociationFacet and
* modification of this AbstractAssociationFacet will not modify the
* returned List. Modifications to the returned List will also not modify
* any future or previous objects returned by this (or other) methods on
* AbstractAssociationFacet. If you wish to modify the information stored in
* this AbstractAssociationFacet, you must use the add*() and remove*()
* methods of AbstractAssociationFacet.
*
* @param id
* The PCGenIdentifier representing the item for which the items
* in this AbstractAssociationFacet should be returned.
* @return A non-null copy of the Set of objects in this
* AbstractAssociationFacet for the item represented by the given
* PCGenIdentifier
*/
public Set<S> getSet(IDT id)
{
Map<S, A> componentMap = getCachedMap(id);
if (componentMap == null)
{
return Collections.emptySet();
}
return Collections
.unmodifiableSet(new ListSet<>(componentMap.keySet()));
}
/**
* Returns the count of items {@literal (objects -> association entries)} in this
* AbstractAssociationFacet for the item represented by the given
* PCGenIdentifier
*
* @param id
* The PCGenIdentifier representing the item for which the count
* of items should be returned
* @return The count of items (objects and thus also associations) in this
* AbstractAssociationFacet for the item represented by the given
* PCGenIdentifier
*/
public int getCount(IDT id)
{
Map<S, A> componentMap = getCachedMap(id);
if (componentMap == null)
{
return 0;
}
return componentMap.size();
}
/**
* Returns true if this AbstractAssociationFacet does not contain any items
* for the item represented by the given PCGenIdentifier
*
* @param id
* The PCGenIdentifier representing the PlayerCharacter to test
* if any items are contained by this AbstractsSourcedListFacet
* @return true if this AbstractAssociationFacet does not contain any items
* for the item represented by the given PCGenIdentifier; false
* otherwise (if it does contain items for the item)
*/
public boolean isEmpty(IDT id)
{
Map<S, A> componentMap = getCachedMap(id);
return componentMap == null || componentMap.isEmpty();
}
/**
* Returns true if this AbstractAssociationFacet contains the given source
* in the list of items for the item represented by the given
* PCGenIdentifier.
*
* @param id
* The PCGenIdentifier representing the item used for testing
* @param obj
* The source object to test if this AbstractAssociationFacet
* contains an association for that item for the item represented
* by the given PCGenIdentifier
* @return true if this AbstractAssociationFacet contains an association for
* the given source for the item represented by the given
* PCGenIdentifier; false otherwise
*/
public boolean contains(IDT id, S obj)
{
Map<S, A> componentMap = getCachedMap(id);
return componentMap != null && componentMap.containsKey(obj);
}
/**
* Returns the type-safe Map for this AbstractAssociationFacet and the given
* PCGenIdentifier. May return null if no information has been set in this
* AbstractAssociationFacet for the given PCGenIdentifier.
*
* Note that this method SHOULD NOT be public. The Map is owned by
* AbstractAssociationFacet, and since it can be modified, a reference to
* that object should not be exposed to any object other than
* AbstractAssociationFacet.
*
* @param id
* The PCGenIdentifier for which the Map should be returned
* @return The Map for the item represented by the given PCGenIdentifier;
* null if no information has been set in this
* AbstractAssociationFacet for the item.
*/
protected Map<S, A> getCachedMap(IDT id)
{
return (Map<S, A>) getCache(id);
}
/**
* Returns a type-safe Map for this AbstractAssociationFacet and the given
* PCGenIdentifier. Will return a new, empty Map if no information has been
* set in this AbstractAssociationFacet for the given PCGenIdentifier. Will
* not return null.
*
* Note that this method SHOULD NOT be public. The Map object is owned by
* AbstractAssociationFacet, and since it can be modified, a reference to
* that object should not be exposed to any object other than
* AbstractAssociationFacet.
*
* @param id
* The PCGenIdentifier for which the Map should be returned
* @return The Map for the item represented by the given PCGenIdentifier.
*/
private Map<S, A> getConstructingCachedMap(IDT id)
{
Map<S, A> componentMap = getCachedMap(id);
if (componentMap == null)
{
componentMap = getComponentMap();
setCache(id, componentMap);
}
return componentMap;
}
/**
* Returns a new (empty) Map for this AbstractAssociationFacet. Can be
* overridden by classes that extend AbstractAssociationFacet if a Map other
* than an IdentityHashMap is desired for storing the information in the
* AbstractAssociationFacet.
*
* Note that this method SHOULD NOT be public. The Map object is owned by
* AbstractAssociationFacet, and since it can be modified, a reference to
* that object should not be exposed to any object other than
* AbstractAssociationFacet.
*
* Note that this method should always be the only method used to construct
* a Map for this AbstractAssociationFacet. It is actually preferred to use
* getConstructingCacheMap(PCGenIdentifier) in order to implicitly call this
* method.
*
* @return A new (empty) Map for use in this AbstractAssociationFacet.
*/
protected Map<S, A> getComponentMap()
{
return new IdentityHashMap<>();
}
/**
* Copies the contents of the AbstractAssociationFacet from one Player
* Character to another item, based on the given PCGenIdentifiers
* representing those items.
*
* This is a method in AbstractAssociationFacet in order to avoid exposing
* the mutable Map object to other classes. This should not be inlined, as
* the Map is internal information to AbstractAssociationFacet and should
* not be exposed to other classes.
*
* Note also the copy is a one-time event and no references are maintained
* between the items represented by the given PCGenIdentifiers (meaning once
* this copy takes place, any change to the AbstractAssociationFacet of one
* item will only impact the item where the AbstractAssociationFacet was
* changed).
*
* @param source
* The PCGenIdentifier representing the item from which the
* information should be copied
* @param destination
* The PCGenIdentifier representing the item to which the
* information should be copied
*/
@Override
public void copyContents(IDT source, IDT destination)
{
Map<S, A> sourceMap = getCachedMap(source);
if (sourceMap != null)
{
getConstructingCachedMap(destination).putAll(sourceMap);
}
}
}