/*
* Copyright (c) 2012 Tom Parker <thpr@users.sourceforge.net>
*
* 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import pcgen.base.test.InequalityTester;
import pcgen.base.util.DoubleKeyMap;
import pcgen.cdom.base.PCGenIdentifier;
import pcgen.util.Logging;
/**
* An AbstractStorageFacet is a facet which stores contents in the overall CDOM
* cache. All classes (facets) that want to store information in the cache must
* extend this class.
*
* @param <T>
* The Type of identifier used in this AbstractStorageFacet
*/
public abstract class AbstractStorageFacet<T extends PCGenIdentifier>
{
private final Class<?> thisClass = getClass();
/**
* Copies the contents of the AbstractStorageFacet from one resource to
* another resource, based on the given PCGenIdentifiers representing those
* resources.
*
* This is a method each AbstractStorageFacet must implement in order to do
* 2 things: First, it must avoid exposing the mutable storage information
* stored in the cache to other classes. Second, this ensures that every
* AbstractStorageFacet has implemented a copy function so that any deep
* copies (if Lists need to be cloned, etc.) is done appropriately.
*
* Note also the copy is a one-time event and no references should be
* maintained between the resources represented by the given
* PCGenIdentifiers (meaning once this copy takes place, any change to the
* AbstractStorageFacet of one resource will only impact the resource where
* the AbstractStorageFacet was changed).
*
* @param source
* The PCGenIdentifier representing the resource from which the
* information should be copied
* @param copy
* The PCGenIdentifier representing the resource to which the
* information should be copied
*/
public abstract void copyContents(T source, T copy);
/**
* The actual cache that stores the CDOM information, as stored by the
* identifying PCGenIdentifier of a resource and the class of the facet
* storing the information
*/
private static final DoubleKeyMap<PCGenIdentifier, Class<?>, Object> CACHE =
new DoubleKeyMap<>(
WeakHashMap.class, HashMap.class);
/*
* Note: the use of CACHE.getReadOnlyMapFor(K1) in peekAtCache makes calling
* CACHE.removeAll(k1) or CACHE.clear() [not used in this class at the
* moment] a rather dangerous activity that is prone to later frustration in
* debugging. It is advised that if such a call is every considered that
* detailed consideration is made of the consequences so that debugging
* information is not destroyed in the process. - thpr Dec 15, 2012.
*/
/**
* Removes the information from the cache for a given resource and facet (as
* identified by the Class)
*
* @param id
* The PCGenIdentifier for which information from the cache
* should be removed
* @return The information which was removed from the Cache for the resource
* identified by the given PCGenIdentifier and the facet identified
* by the given Class.
*/
public Object removeCache(T id)
{
if (id == null)
{
throw new IllegalArgumentException(
"PCGenIdentifier cannot be null in removeCache");
}
return CACHE.remove(id, thisClass);
}
/**
* Sets the information from the cache for a given resource and facet (as
* identified by the Class)
*
* @param id
* The PCGenIdentifier for which information from the cache
* should be removed
* @param o
* The object to be stored in the cache.
* @return The previous information which was removed from the Cache for the
* resource identified by the given PCGenIdentifier and the facet
* identified by the given Class.
*/
public Object setCache(T id, Object o)
{
if (id == null)
{
throw new IllegalArgumentException(
"PCGenIdentifier cannot be null in setCache");
}
return CACHE.put(id, thisClass, o);
}
/**
* Retrieves the information from the cache for a given resource and facet
* (as identified by the Class)
*
* @param id
* The PCGenIdentifier for which information from the cache
* should be removed
* @return The information in the Cache for the resource identified by the
* given PCGenIdentifier and the facet identified by the given
* Class.
*/
public Object getCache(T id)
{
if (id == null)
{
throw new IllegalArgumentException(
"PCGenIdentifier cannot be null in getCache");
}
return CACHE.get(id, thisClass);
}
/**
* Tests whether the contents of the cache are equal for two resources, as
* identified by the PCGenIdentifier objects. The given InequalityTester is
* used to compare the cache contents.
*
* @param id1
* The PCGenIdentifier of the first resource that is to be
* compared
* @param id2
* The PCGenIdentifier of the second resource that is to be
* compared
* @param t
* The InequalityTester used to establish equality between
* contents of the cache.
* @return true if the contents of the cache are equal (as identified by the
* given InequalityTester) for the resources identified by the given
* PCGenIdentifiers; false otherwise
*/
public static boolean areEqualCache(PCGenIdentifier id1,
PCGenIdentifier id2, InequalityTester t)
{
if (id1 == null)
{
throw new IllegalArgumentException(
"PCGenIdentifier #1 cannot be null in areEqualCache");
}
if (id2 == null)
{
throw new IllegalArgumentException(
"PCGenIdentifier #2 cannot be null in areEqualCache");
}
Set<Class<?>> set1 = CACHE.getSecondaryKeySet(id1);
Set<Class<?>> set2 = CACHE.getSecondaryKeySet(id2);
if (!set1.equals(set2))
{
List<Class<?>> l1 = new ArrayList<>(set1);
l1.removeAll(set2);
List<Class<?>> l2 = new ArrayList<>(set2);
l2.removeAll(set1);
Logging.errorPrint("Inequal: " + l1 + " " + l2);
return false;
}
for (Class<?> cl : set1)
{
Object obj1 = CACHE.get(id1, cl);
Object obj2 = CACHE.get(id2, cl);
String equal = t.testEquality(obj1, obj2, cl + "/");
if (equal != null)
{
Logging.errorPrint(equal);
return false;
}
}
return true;
}
/**
* Returns a read-only view into the cache for a given PCGenIdentifier.
*
* Since the returned Map is read-only, the value here is in that it is a
* direct reference to the contents of cache for a given PCGenIdentifier,
* and is therefore reference-semantic (the contents of the returned map
* will change as the contents of the cache are changed). Ownership of the
* returned Map is transferred to the caller, although since it is
* read-only, that is perhaps only relevant for determining the garbage
* collection time of the decorator that makes the returned Map an
* unmodifiable view into this DoubleKeyMap.
*
* Note that while this is a read-only map, there is no guarantee that this
* returned map is thread-safe. Use in threaded situations with caution.
*
* @param id
* The PCGenIdentifier for which a read-only view of the cache
* should be returned.
* @return A read-only view of the cache for the given PCGenIdentifier
*/
public static Map<Class<?>, Object> peekAtCache(PCGenIdentifier id)
{
if (id == null)
{
throw new IllegalArgumentException(
"PCGenIdentifier cannot be null in peekAtCache");
}
return CACHE.getReadOnlyMapFor(id);
}
}