/*
* Copyright 2008 (C) Tom Parker <thpr@users.sourceforge.net>
*
* This library 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 library 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.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package pcgen.rules.persistence.util;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import pcgen.base.lang.CaseInsensitiveString;
import pcgen.base.lang.UnreachableError;
import pcgen.base.util.CaseInsensitiveMap;
import pcgen.base.util.DoubleKeyMap;
import pcgen.base.util.TripleKeyMap;
import pcgen.cdom.base.GroupDefinition;
import pcgen.cdom.base.Loadable;
import pcgen.persistence.lst.prereq.PrerequisiteParserInterface;
import pcgen.rules.persistence.token.CDOMSecondaryToken;
import pcgen.rules.persistence.token.CDOMSubToken;
import pcgen.rules.persistence.token.CDOMToken;
import pcgen.rules.persistence.token.DeferredToken;
import pcgen.util.Logging;
/**
* A TokenFamily represents a set of tokens applicable to a specific Revision of
* PCGen. This class also acts as the overall library of TokenFamily objects.
*/
public final class TokenFamily implements Comparable<TokenFamily>
{
/**
* The TokenFamily for the current version of PCGen (the one being run)
*/
public static final TokenFamily CURRENT = new TokenFamily(new Revision(
Integer.MAX_VALUE, 0, 0));
/**
* The TokenFamily for Revision 5.14 of PCGen. This version has specific
* compatibility around things like using PCClass tokens on PCClassLevel
* lines, so this revision requires some hardcoding (vs other Revisions that
* are dynamically generated by compatibility tokens)
*/
public static final TokenFamily REV514 = new TokenFamily(new Revision(5,
14, Integer.MIN_VALUE));
/**
* The Library of TokenFamily objects available for use
*/
private static SortedMap<Revision, TokenFamily> typeMap;
/**
* The Revision of PCGen for which this TokenFamily is used or is providing
* compatibility
*/
private final Revision rev;
/**
* The CDOMTokens available in this TokenFamily. This is stored by the class
* where the token can be used and the token name.
*/
private final DoubleKeyMap<Class<?>, String, CDOMToken<?>> tokenMap =
new DoubleKeyMap<>();
/**
* The CDOMSecondaryTokens available in this TokenFamily. This is stored by
* the class where the token can be used, the "primary/parent" token name
* and the subtoken name.
*/
private final TripleKeyMap<Class<?>, String, String, CDOMSecondaryToken<?>> subTokenMap =
new TripleKeyMap<>(HashMap.class, CaseInsensitiveMap.class, CaseInsensitiveMap.class);
/**
* The PRExxx tokens available in this TokenFamily. This is stored by the
* token name.
*/
private final Map<CaseInsensitiveString, PrerequisiteParserInterface> preTokenMap =
new HashMap<>();
/**
* The DeferredTokens defined for this TokenFamily. This set of tokens will
* be run after the initial pass of the data load is complete
*/
private final List<DeferredToken<? extends Loadable>> deferredTokenList =
new ArrayList<>();
/**
* The Group Definitions available for this TokenFamily. These are FACT and
* FACTSET items. These are stored by the Class in which they are usable and
* the Token name.
*/
private final DoubleKeyMap<Class<?>, String, GroupDefinition<?>> groupDefinitionMap =
new DoubleKeyMap<>(HashMap.class, CaseInsensitiveMap.class);
/**
* Constructs a new TokenFamily for the given Revision.
*
* @param r The Revision of PCGen covered by this TokenFamily
*/
public TokenFamily(Revision r)
{
rev = r;
}
/**
* Adds a new Token to this TokenLibrary.
*
* @param tok
* The CDOMToken to be added to this TokenLibrary
*
* @return The previous CDOMToken stored in this TokenLibrary for the same
* Token Class and Token Name; null if no CDOMToken had previously
* been stored for that combination
*/
@SuppressWarnings("unchecked")
public <T> CDOMToken<T> putToken(CDOMToken<T> tok)
{
if (tok.getTokenClass() == null)
{
Logging.errorPrint("Cannot load token "
+ tok.getClass().getSimpleName() + " with no token class");
}
return (CDOMToken<T>) tokenMap.put(tok.getTokenClass(), tok
.getTokenName(), tok);
}
/**
* Returns the CDOMToken for the given Class and with the given token name.
*
* @param cl
* The Class for which a token should be retrieved
* @param name
* The name of the token to be retrieved
* @return the CDOMToken for the given Class and with the given token name
*/
public CDOMToken<?> getToken(Class<?> cl, String name)
{
return tokenMap.get(cl, name);
}
/**
* Returns all the tokens in this TokenLibrary for the given Class.
*
* @param cl
* The Class for which the tokens in this library should be
* returned
*
* @return A set of the tokens in this TokenLibrary for the given Class
*/
public Set<CDOMToken<?>> getTokens(Class<?> cl)
{
return tokenMap.values(cl);
}
/**
* Adds a new SubToken to this TokenLibrary.
*
* @param tok
* The CDOMSecondaryToken to be added to this TokenLibrary
*
* @return The previous CDOMSecondaryToken stored in this TokenLibrary for
* the same Token Class, Parent Token, and Token Name; null if no
* CDOMSecondaryToken had previously been stored for that
* combination
*/
@SuppressWarnings("unchecked")
public <U, T extends CDOMSecondaryToken<U>> CDOMSecondaryToken<U> putSubToken(T tok)
{
if (tok.getTokenClass() == null)
{
Logging.errorPrint("Cannot load token "
+ tok.getClass().getSimpleName() + " with no token class");
}
return (CDOMSecondaryToken<U>) subTokenMap.put(tok.getTokenClass(), tok
.getParentToken(), tok.getTokenName(), tok);
}
/**
* Returns the CDOMSubToken for the given Class and parent token, and with
* the given token name.
*
* @param cl
* The Class for which a token should be retrieved
* @param token
* The name of the parent token for the CDOMSubToken to be
* returned
* @param key
* The name of the sub token to be retrieved
* @return the CDOMSubToken for the given Class and parent token, and with
* the given token name
*/
@SuppressWarnings("unchecked")
public <T> CDOMSubToken<? super T> getSubToken(Class<? extends T> cl, String token,
String key)
{
return (CDOMSubToken<? super T>) subTokenMap.get(cl, token, key);
}
/**
* Returns all the tokens in this TokenLibrary for the given Class and
* parent token name.
*
* @param cl
* The Class for which the tokens in this library should be
* returned
* @param token
* The name of the parent token for the CDOMSubTokens to be
* returned
*
* @return A Set of the subtokens in this TokenLibrary for the given Class
* and parent token name
*/
@SuppressWarnings("unchecked")
public <T> Set<CDOMSecondaryToken<? super T>> getSubTokens(Class<? super T> cl, String token)
{
return (Set) subTokenMap.values(cl, token);
}
/**
* Adds a Prerequisite Parser Token to this TokenLibrary.
*
* @param token
* The Prerequisite Parser Token to be added to this TokenLibrary
*/
public void putPrerequisiteToken(PrerequisiteParserInterface token)
{
for (String s : token.kindsHandled())
{
preTokenMap.put(new CaseInsensitiveString(s), token);
}
}
/**
* Returns the Prerequisite Parser Token for the given Prerequisite token
* name.
*
* @param key
* The Prerequisite token name for which the Prerequisite Parser
* Token should be returned
* @return The Prerequisite Parser Token for the given Prerequisite token
* name
*/
public PrerequisiteParserInterface getPrerequisiteToken(String key)
{
return preTokenMap.get(new CaseInsensitiveString(key));
}
/**
* Constructs a new TokenFamily with the given primary, secondary and
* tertiary values as the Sequence characteristics
*
* @return The new TokenFamily built with the given primary, secondary and
* tertiary values
*/
public static TokenFamily getConstant(int primary, int secondary,
int tertiary)
{
if (typeMap == null)
{
buildMap();
}
Revision r = new Revision(primary, secondary, tertiary);
TokenFamily o = typeMap.get(r);
if (o == null)
{
o = new TokenFamily(r);
typeMap.put(r, o);
}
return o;
}
/**
* Actually build the set of Constants, using any "public static final"
* constants within the child (extending) class as initial values in the
* Constant pool.
*/
private static void buildMap()
{
typeMap = new TreeMap<>();
Class<TokenFamily> cl = TokenFamily.class;
Field[] fields = cl.getDeclaredFields();
for (int i = 0; i < fields.length; i++)
{
int mod = fields[i].getModifiers();
if (Modifier.isStatic(mod) && Modifier.isFinal(mod)
&& Modifier.isPublic(mod))
{
try
{
Object o = fields[i].get(null);
if (cl.equals(o.getClass()))
{
TokenFamily tObj = cl.cast(o);
if (typeMap.containsKey(tObj.rev))
{
throw new UnreachableError(
"Attempt to redefine constant value "
+ tObj.rev + " to "
+ fields[i].getName()
+ ", value was "
+ typeMap.get(tObj.rev));
}
typeMap.put(tObj.rev, tObj);
}
}
catch (IllegalArgumentException e)
{
throw new UnreachableError(
"Attempt to fetch field failed: " + e.getMessage());
}
catch (IllegalAccessException e)
{
throw new UnreachableError(
"Attempt to fetch field failed for access: "
+ e.getMessage());
}
}
}
}
/**
* Clears all of the Constants defined by this class. Note that this does
* not remove any Constants declared in the Constant class (as those are
* considered 'permanent' members of the Sequenced Constant collection.
*
* Note that this *will not* reset the ordinal count, because that is a
* dangerous operation. As there could be outstanding references to
* constants that would be removed from the Constant pool, no reuse of
* ordinals is driven by this method. As a result, calling this method may
* result in a Constant Pool which does not have sequentially numbered
* ordinal values.
*/
public static void clearConstants()
{
buildMap();
}
/**
* Returns a Collection of all of the Constants for this class. The returned
* Collection is unmodifiable.
*
* @return an unmodifiable Collection of all of the Constants for this class
*/
public static Collection<TokenFamily> getAllConstants()
{
return Collections.unmodifiableCollection(typeMap.values());
}
/**
* @see java.lang.Comparable#compareTo(java.lang.Object)
*/
@Override
public int compareTo(TokenFamily tf)
{
return rev.compareTo(tf.rev);
}
/*
* Note there is no reason to do .hashCode or .equals because this is Type
* Safe (meaning it can only build one object per Revision)
*/
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return "Token Family: " + rev.toString();
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
return obj == this || obj instanceof TokenFamily
&& compareTo((TokenFamily) obj) == 0;
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return rev.hashCode();
}
public void clearTokens()
{
tokenMap.clear();
subTokenMap.clear();
deferredTokenList.clear();
preTokenMap.clear();
}
/**
* Returns all the DeferredTokens in this TokenLibrary.
*
* @return A List of the DeferredToken in this TokenLibrary
*/
public List<DeferredToken<? extends Loadable>> getDeferredTokens()
{
return new ArrayList<>(
deferredTokenList);
}
/**
* Adds a new DeferredToken to this TokenLibrary.
*
* @param newToken
* The DeferredToken to be added to this TokenLibrary
*/
public void addDeferredToken(DeferredToken<?> newToken)
{
deferredTokenList.add(newToken);
}
/**
* Adds a new GroupDefinition to this TokenFamily. A GroupDefinition can
* produce an ObjectContainer (grouping of objects) based on an underlying
* set of requirements (typically defined in the Data Control file).
*
* @param def
* The GroupDefinition to be added to this TokenFamily.
*/
public void addGroupDefinition(GroupDefinition<?> def)
{
GroupDefinition<?> existingDef =
groupDefinitionMap.put(def.getReferenceClass(),
def.getPrimitiveName(), def);
if (existingDef != null)
{
Logging.errorPrint("Duplicate Group Definition in "
+ def.getReferenceClass().getSimpleName() + ": "
+ def.getPrimitiveName() + ". Classes were "
+ existingDef.getClass().getName() + " and "
+ def.getClass().getName());
}
}
/**
* Returns the GroupDefinition for the given Class, and with the given group
* name.
*
* @param cl
* The Class for which a token should be retrieved
* @param name
* The name of the GroupDefinition to be retrieved
* @return the GroupDefinition for the given Class, and with the given group
* name
*/
@SuppressWarnings("unchecked")
public <T> GroupDefinition<T> getGroup(Class<T> cl, String name)
{
return (GroupDefinition<T>) groupDefinitionMap.get(cl, name);
}
}