/*
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;
import pcgen.base.lang.UnreachableError;
import pcgen.base.util.CaseInsensitiveMap;
import pcgen.base.util.DoubleKeyMap;
import pcgen.base.util.TreeMapToList;
import pcgen.cdom.base.CDOMObject;
import pcgen.cdom.base.GroupDefinition;
import pcgen.cdom.base.Loadable;
import pcgen.core.PCClass;
import pcgen.core.bonus.BonusObj;
import pcgen.persistence.lst.LstToken;
import pcgen.persistence.lst.prereq.PreMultParser;
import pcgen.persistence.lst.prereq.PrerequisiteParserInterface;
import pcgen.rules.persistence.token.CDOMCompatibilityToken;
import pcgen.rules.persistence.token.CDOMPrimaryToken;
import pcgen.rules.persistence.token.CDOMSecondaryToken;
import pcgen.rules.persistence.token.CDOMSubToken;
import pcgen.rules.persistence.token.CDOMToken;
import pcgen.rules.persistence.token.ClassWrappedToken;
import pcgen.rules.persistence.token.DeferredToken;
import pcgen.rules.persistence.token.ModifierFactory;
import pcgen.rules.persistence.token.PostDeferredToken;
import pcgen.rules.persistence.token.PostValidationToken;
import pcgen.rules.persistence.token.PreCompatibilityToken;
import pcgen.rules.persistence.token.PrimitiveToken;
import pcgen.rules.persistence.token.QualifierToken;
import pcgen.rules.persistence.util.TokenFamily;
import pcgen.system.PluginLoader;
import pcgen.util.Logging;
public final class TokenLibrary implements PluginLoader
{
private static final Class<PCClass> PCCLASS_CLASS = PCClass.class;
private static final Class<CDOMObject> CDOMOBJECT_CLASS = CDOMObject.class;
private static final TreeMapToList<Integer, PostDeferredToken<? extends Loadable>> POST_DEFERRED_TOKENS =
new TreeMapToList<>();
private static final TreeMapToList<Integer, PostValidationToken<? extends Loadable>> POST_VALIDATION_TOKENS =
new TreeMapToList<>();
private static final DoubleKeyMap<Class<?>, String, Class<? extends QualifierToken<?>>> QUALIFIER_MAP =
new DoubleKeyMap<>();
private static final DoubleKeyMap<Class<?>, String, Class<? extends PrimitiveToken<?>>> PRIMITIVE_MAP =
new DoubleKeyMap<>();
private static final DoubleKeyMap<Class<?>, String, ModifierFactory<?>> modifierMap =
new DoubleKeyMap<>();
private static final Set<TokenFamily> TOKEN_FAMILIES = new TreeSet<>();
private static final CaseInsensitiveMap<Class<? extends BonusObj>> BONUS_TAG_MAP =
new CaseInsensitiveMap<>();
private static TokenLibrary instance = null;
static
{
reset();
}
public static void reset()
{
POST_DEFERRED_TOKENS.clear();
QUALIFIER_MAP.clear();
PRIMITIVE_MAP.clear();
BONUS_TAG_MAP.clear();
TOKEN_FAMILIES.clear();
TokenFamily.CURRENT.clearTokens();
TOKEN_FAMILIES.add(TokenFamily.CURRENT);
TokenFamily.REV514.clearTokens();
TOKEN_FAMILIES.add(TokenFamily.REV514);
addToTokenMap(new PreMultParser());
}
private TokenLibrary()
{
// Don't instantiate utility class
}
public static <T> PrimitiveToken<T> getPrimitive(Class<T> cl, String tokKey)
{
Iterator<PrimitiveToken<T>> it =
new PrimitiveTokenIterator<>(cl, tokKey);
if (it.hasNext())
{
return it.next();
}
return null;
}
public static Collection<PostDeferredToken<? extends Loadable>> getPostDeferredTokens()
{
List<PostDeferredToken<? extends Loadable>> list =
new ArrayList<>();
for (Integer key : POST_DEFERRED_TOKENS.getKeySet())
{
list.addAll(POST_DEFERRED_TOKENS.getListFor(key));
}
return list;
}
public static void addToModifierMap(ModifierFactory<?> m)
{
if (ModifierFactory.class.isAssignableFrom(m.getClass()))
{
String name = m.getIdentification();
Class<?> cl = m.getVariableFormat();
ModifierFactory<?> prev = modifierMap.put(cl, name, m);
if (prev != null)
{
Logging.errorPrint("Found a second " + name + " Modifier for "
+ cl);
}
}
}
public static <T> ModifierFactory<T> getModifier(Class<T> cl, String tokKey)
{
for (Iterator<ModifierFactory<T>> it =
new ModifierIterator<>(cl, tokKey); it.hasNext();)
{
return it.next();
}
return null;
}
public static Collection<PostValidationToken<? extends Loadable>> getPostValidationTokens()
{
List<PostValidationToken<? extends Loadable>> list =
new ArrayList<>();
for (Integer key : POST_VALIDATION_TOKENS.getKeySet())
{
list.addAll(POST_VALIDATION_TOKENS.getListFor(key));
}
return list;
}
public static void addToPrimitiveMap(PrimitiveToken<?> p)
{
Class newTokClass = p.getClass();
if (PrimitiveToken.class.isAssignableFrom(newTokClass))
{
String name = p.getTokenName();
Class cl = p.getReferenceClass();
Class<? extends PrimitiveToken<?>> prev =
PRIMITIVE_MAP.put(cl, name, newTokClass);
if (prev != null)
{
Logging.errorPrint("Found a second " + name + " Primitive for " + cl);
}
}
}
public static void addToQualifierMap(QualifierToken<?> p)
{
Class newTokClass = p.getClass();
Class<?> cl = p.getReferenceClass();
String name = p.getTokenName();
Class<? extends QualifierToken> prev = QUALIFIER_MAP.put(cl, name, newTokClass);
if (prev != null)
{
Logging.errorPrint("Found a second " + name + " Qualifier for " + cl);
}
}
public static void addToTokenMap(Object newToken)
{
if (newToken instanceof PostDeferredToken)
{
PostDeferredToken<?> pdt = (PostDeferredToken<?>) newToken;
POST_DEFERRED_TOKENS.addToListFor(pdt.getPriority(), pdt);
}
if (newToken instanceof PostValidationToken)
{
PostValidationToken<?> pdt = (PostValidationToken<?>) newToken;
POST_VALIDATION_TOKENS.addToListFor(pdt.getPriority(), pdt);
}
if (newToken instanceof CDOMCompatibilityToken)
{
CDOMCompatibilityToken<?> tok = (CDOMCompatibilityToken<?>) newToken;
TokenFamily fam =
TokenFamily.getConstant(tok.compatibilityLevel(),
tok.compatibilitySubLevel(), tok.compatibilityPriority());
if (fam.putToken(tok) != null)
{
Logging.errorPrint("Duplicate " + tok.getTokenClass().getSimpleName()
+ " Compatibility Token found for token " + tok.getTokenName()
+ " at compatibility level " + tok.compatibilityLevel() + "."
+ tok.compatibilitySubLevel() + "." + tok.compatibilityPriority());
}
TOKEN_FAMILIES.add(fam);
if (fam.compareTo(TokenFamily.REV514) <= 0 && PCCLASS_CLASS.equals(tok.getTokenClass()))
{
@SuppressWarnings("unchecked")
CDOMCompatibilityToken<PCClass> clTok =
(CDOMCompatibilityToken<PCClass>) tok;
addToTokenMap(new ClassWrappedToken(clTok));
}
}
loadFamily(TokenFamily.CURRENT, newToken);
}
public static void loadFamily(TokenFamily family, Object newToken)
{
if (newToken instanceof DeferredToken)
{
family.addDeferredToken((DeferredToken<?>) newToken);
}
if (newToken instanceof CDOMPrimaryToken)
{
CDOMPrimaryToken<?> tok = (CDOMPrimaryToken<?>) newToken;
CDOMToken<?> existingToken = family.putToken(tok);
if (existingToken != null)
{
Logging.errorPrint("Duplicate "
+ tok.getTokenClass().getSimpleName()
+ " Token found for token " + tok.getTokenName()
+ ". Classes were " + existingToken.getClass().getName()
+ " and " + newToken.getClass().getName());
}
if (PCCLASS_CLASS.equals(tok.getTokenClass()))
{
@SuppressWarnings("unchecked")
CDOMPrimaryToken<PCClass> clTok =
(CDOMPrimaryToken<PCClass>) tok;
addToTokenMap(new ClassWrappedToken(clTok));
}
}
if (newToken instanceof CDOMSecondaryToken)
{
CDOMSecondaryToken<?> tok = (CDOMSecondaryToken<?>) newToken;
CDOMSubToken<?> existingToken = family.putSubToken(tok);
if (existingToken != null)
{
Logging.errorPrint("Duplicate "
+ tok.getTokenClass().getSimpleName()
+ " Token found for token " + tok.getParentToken() + ":"
+ tok.getTokenName() + ". Classes were "
+ existingToken.getClass().getName() + " and "
+ newToken.getClass().getName());
}
}
if (newToken instanceof PrerequisiteParserInterface)
{
PrerequisiteParserInterface prereqToken = (PrerequisiteParserInterface) newToken;
family.putPrerequisiteToken(prereqToken);
for (String s : prereqToken.kindsHandled())
{
/*
* TODO Theoretically these belong in REV514, but put into
* current for unparse testing
*/
PreCompatibilityToken pos =
new PreCompatibilityToken(s, prereqToken, false);
if (family.putToken(pos) != null)
{
Logging.errorPrint("Duplicate " + pos.getTokenClass().getSimpleName()
+ " Token found for token " + pos.getTokenName());
}
if (family.putSubToken(pos) != null)
{
Logging.errorPrint("Duplicate " + pos.getTokenClass().getSimpleName()
+ " Token found for token " + pos.getParentToken() + ":"
+ pos.getTokenName());
}
family.putSubToken(pos);
PreCompatibilityToken neg =
new PreCompatibilityToken(s, prereqToken, true);
if (family.putToken(neg) != null)
{
Logging.errorPrint("Duplicate " + neg.getTokenClass().getSimpleName()
+ " Token found for token " + neg.getTokenName());
}
if (family.putSubToken(neg) != null)
{
Logging.errorPrint("Duplicate " + neg.getTokenClass().getSimpleName()
+ " Token found for token " + neg.getParentToken() + ":"
+ neg.getTokenName());
}
}
}
if (newToken instanceof GroupDefinition)
{
family.addGroupDefinition((GroupDefinition<?>) newToken);
}
}
public static TokenLibrary getInstance()
{
if (instance == null)
{
instance = new TokenLibrary();
}
return instance;
}
@Override
public void loadPlugin(Class<?> clazz) throws Exception
{
if (BonusObj.class.isAssignableFrom(clazz))
{
addBonusClass(clazz);
}
Object token = clazz.newInstance();
if (LstToken.class.isAssignableFrom(clazz)
|| PrerequisiteParserInterface.class.isAssignableFrom(clazz))
{
addToTokenMap(token);
}
if (QualifierToken.class.isAssignableFrom(clazz))
{
addToQualifierMap((QualifierToken<?>) token);
}
if (PrimitiveToken.class.isAssignableFrom(clazz))
{
addToPrimitiveMap((PrimitiveToken<?>) token);
}
if (ModifierFactory.class.isAssignableFrom(clazz))
{
addToModifierMap((ModifierFactory<?>) token);
}
}
@Override
public Class[] getPluginClasses()
{
return new Class[]
{
LstToken.class,
BonusObj.class,
PrerequisiteParserInterface.class,
ModifierFactory.class
};
}
abstract static class AbstractTokenIterator<C, T> implements Iterator<T>
{
// private static final Class<Object> OBJECT_CLASS = Object.class;
private final Class<C> rootClass;
private final String tokenKey;
private T nextToken = null;
private boolean needNewToken = true;
private Class<?> stopClass;
private final Iterator<TokenFamily> subIterator;
public AbstractTokenIterator(Class<C> cl, String key)
{
rootClass = cl;
subIterator = TOKEN_FAMILIES.iterator();
tokenKey = key;
}
@Override
public boolean hasNext()
{
setNextToken();
return !needNewToken;
}
protected void setNextToken()
{
while (needNewToken && subIterator.hasNext())
{
TokenFamily family = subIterator.next();
Class<?> actingClass = rootClass;
nextToken = grabToken(family, actingClass, tokenKey);
while (nextToken == null && actingClass != null && !actingClass.equals(stopClass))
{
actingClass = actingClass.getSuperclass();
nextToken = grabToken(family, actingClass, tokenKey);
}
if (stopClass == null)
{
stopClass = actingClass;
}
needNewToken = nextToken == null;
}
}
protected abstract T grabToken(TokenFamily family, Class<?> cl,
String key);
@Override
public T next()
{
setNextToken();
if (needNewToken)
{
throw new NoSuchElementException();
}
needNewToken = true;
return nextToken;
}
@Override
public void remove()
{
throw new UnsupportedOperationException(
"Iterator does not support remove");
}
}
static class TokenIterator<C extends Loadable, T extends CDOMToken<? super C>>
extends TokenLibrary.AbstractTokenIterator<C, T>
{
public TokenIterator(Class<C> cl, String key)
{
super(cl, key);
}
@Override
protected T grabToken(TokenFamily family, Class<?> cl, String key)
{
return (T) family.getToken(cl, key);
}
}
static class SubTokenIterator<C, T extends CDOMSubToken<? super C>> extends
TokenLibrary.AbstractTokenIterator<C, T>
{
private final String subTokenKey;
public SubTokenIterator(Class<C> cl, String key, String subKey)
{
super(cl, key);
subTokenKey = subKey;
}
@Override
protected T grabToken(TokenFamily family, Class<?> cl, String key)
{
return (T) family.getSubToken(cl, key, subTokenKey);
}
}
static class QualifierTokenIterator<C extends CDOMObject, T extends QualifierToken<? super C>>
extends TokenLibrary.AbstractTokenIterator<C, T>
{
public QualifierTokenIterator(Class<C> cl, String key)
{
super(cl, key);
}
@Override
protected T grabToken(TokenFamily family, Class<?> cl, String key)
{
if (!TokenFamily.CURRENT.equals(family))
{
return null;
}
Class<? extends QualifierToken> cl1 = QUALIFIER_MAP.get(cl, key);
if (cl1 == null)
{
return null;
}
try
{
return (T) cl1.newInstance();
}
catch (InstantiationException e)
{
throw new UnreachableError("new Instance on " + cl1 + " should not fail", e);
}
catch (IllegalAccessException e)
{
throw new UnreachableError("new Instance on " + cl1
+ " should not fail due to access", e);
}
}
}
static class PrimitiveTokenIterator<C, T extends PrimitiveToken<? super C>>
extends TokenLibrary.AbstractTokenIterator<C, T>
{
public PrimitiveTokenIterator(Class<C> cl, String key)
{
super(cl, key);
}
@Override
protected T grabToken(TokenFamily family, Class<?> cl, String key)
{
if (!TokenFamily.CURRENT.equals(family))
{
return null;
}
Class<? extends PrimitiveToken> cl1 = PRIMITIVE_MAP.get(cl, key);
if (cl1 == null)
{
return null;
}
try
{
return (T) cl1.newInstance();
}
catch (InstantiationException e)
{
throw new UnreachableError("new Instance on " + cl1 + " should not fail", e);
}
catch (IllegalAccessException e)
{
throw new UnreachableError("new Instance on " + cl1
+ " should not fail due to access", e);
}
}
}
static class ModifierIterator<C, T extends ModifierFactory<? super C>>
extends TokenLibrary.AbstractTokenIterator<C, T>
{
public ModifierIterator(Class<C> cl, String key)
{
super(cl, key);
}
@Override
protected T grabToken(TokenFamily family, Class<?> cl, String key)
{
if (!TokenFamily.CURRENT.equals(family))
{
return null;
}
return (T) modifierMap.get(cl, key);
}
}
static class PreTokenIterator
extends TokenLibrary.AbstractTokenIterator<CDOMObject, PrerequisiteParserInterface>
{
public PreTokenIterator(String key)
{
super(CDOMOBJECT_CLASS, key);
}
@Override
protected PrerequisiteParserInterface grabToken(TokenFamily family, Class<?> cl,
String key)
{
return family.getPrerequisiteToken(key);
}
}
/**
* Add a CLASS via a BONUS
*
* @return true if successful
*/
public static boolean addBonusClass(Class bonusClass) throws InstantiationException,
IllegalAccessException
{
if (BonusObj.class.isAssignableFrom(bonusClass))
{
final BonusObj bonusObj = (BonusObj) bonusClass.newInstance();
BONUS_TAG_MAP.put(bonusObj.getBonusHandled(), bonusClass);
return true;
}
return false;
}
public static Class<? extends BonusObj> getBonus(String bonusName)
{
return BONUS_TAG_MAP.get(bonusName);
}
}