/*
* Copyright 2007 (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.context;
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import pcgen.base.formatmanager.FormatUtilities;
import pcgen.base.formatmanager.SimpleFormatManagerLibrary;
import pcgen.base.util.DoubleKeyMap;
import pcgen.base.util.FormatManager;
import pcgen.base.util.Indirect;
import pcgen.base.util.ObjectDatabase;
import pcgen.cdom.base.CDOMObject;
import pcgen.cdom.base.Categorized;
import pcgen.cdom.base.CategorizedClassIdentity;
import pcgen.cdom.base.Category;
import pcgen.cdom.base.ClassIdentity;
import pcgen.cdom.base.Loadable;
import pcgen.cdom.enumeration.FactKey;
import pcgen.cdom.enumeration.IntegerKey;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.enumeration.SubClassCategory;
import pcgen.cdom.enumeration.Type;
import pcgen.cdom.format.table.ColumnFormatFactory;
import pcgen.cdom.format.table.TableFormatFactory;
import pcgen.cdom.list.ClassSkillList;
import pcgen.cdom.list.ClassSpellList;
import pcgen.cdom.list.DomainSpellList;
import pcgen.cdom.reference.CDOMDirectSingleRef;
import pcgen.cdom.reference.CDOMGroupRef;
import pcgen.cdom.reference.CDOMSingleRef;
import pcgen.cdom.reference.ManufacturableFactory;
import pcgen.cdom.reference.ReferenceManufacturer;
import pcgen.cdom.reference.UnconstructedValidator;
import pcgen.cdom.util.IntegerKeyComparator;
import pcgen.core.Domain;
import pcgen.core.Globals;
import pcgen.core.PCClass;
import pcgen.core.SubClass;
import pcgen.util.Logging;
public abstract class AbstractReferenceContext implements ObjectDatabase
{
@SuppressWarnings("rawtypes")
private static final Class<Categorized> CATEGORIZED_CLASS = Categorized.class;
private static final Class<DomainSpellList> DOMAINSPELLLIST_CLASS = DomainSpellList.class;
private static final Class<ClassSkillList> CLASSSKILLLIST_CLASS = ClassSkillList.class;
private static final Class<ClassSpellList> CLASSSPELLLIST_CLASS = ClassSpellList.class;
private static final Class<SubClass> SUBCLASS_CLASS = SubClass.class;
private DoubleKeyMap<Class<?>, Object, WeakReference<List<?>>> sortedMap =
new DoubleKeyMap<>();
private final Map<CDOMObject, CDOMSingleRef<?>> directRefCache = new HashMap<>();
private URI sourceURI;
private URI extractURI;
private final SimpleFormatManagerLibrary fmtLibrary = new SimpleFormatManagerLibrary();
public AbstractReferenceContext()
{
FormatUtilities.loadDefaultFormats(fmtLibrary);
fmtLibrary.addFormatManagerBuilder(new ColumnFormatFactory(this));
fmtLibrary.addFormatManagerBuilder(new TableFormatFactory(this));
}
public abstract <T extends Loadable> ReferenceManufacturer<T> getManufacturer(
Class<T> cl);
public abstract <T extends Loadable> boolean hasManufacturer(Class<T> cl);
protected abstract <T extends Categorized<T>> boolean hasManufacturer(
Class<T> cl, Category<T> cat);
protected final <T extends Loadable> ReferenceManufacturer<T> getNewReferenceManufacturer(
Class<T> cl)
{
ReferenceManufacturer<T> mfg = constructReferenceManufacturer(cl);
if (mfg.getIdentifierType() != null)
{
fmtLibrary.addFormatManager(mfg);
}
return mfg;
}
protected abstract <T extends Loadable> ReferenceManufacturer<T> constructReferenceManufacturer(
Class<T> cl);
/**
* Retrieve the Reference manufacturer that handles this class and category. Note that
* even though abilities are categorized, the category may not be know initially, so
* null cat values are legal.
*
* @return The reference manufacturer
*/
public abstract Collection<? extends ReferenceManufacturer<?>> getAllManufacturers();
public boolean validate(UnconstructedValidator validator)
{
boolean returnGood = true;
for (ReferenceManufacturer<?> ref : getAllManufacturers())
{
returnGood &= ref.validate(validator);
}
return returnGood;
}
public <T extends Loadable> CDOMGroupRef<T> getCDOMAllReference(Class<T> c)
{
return getManufacturer(c).getAllReference();
}
public <T extends Categorized<T>> CDOMGroupRef<T> getCDOMAllReference(
Class<T> c, Category<T> cat)
{
return getManufacturer(c, cat).getAllReference();
}
public <T extends Loadable> CDOMGroupRef<T> getCDOMTypeReference(
Class<T> c, String... val)
{
return getManufacturer(c).getTypeReference(val);
}
public <T extends Categorized<T>> CDOMGroupRef<T> getCDOMTypeReference(
Class<T> c, Category<T> cat, String... val)
{
return getManufacturer(c, cat).getTypeReference(val);
}
public <T extends Loadable> T constructCDOMObject(Class<T> c, String val)
{
T obj;
if (CATEGORIZED_CLASS.isAssignableFrom(c))
{
Class cl = c;
obj = (T) getManufacturer(cl, null).constructObject(val);
}
else
{
obj = getManufacturer(c).constructObject(val);
}
obj.setSourceURI(sourceURI);
return obj;
}
public <T extends Loadable> void constructIfNecessary(Class<T> cl,
String value)
{
getManufacturer(cl).constructIfNecessary(value);
}
public <T extends Loadable> CDOMSingleRef<T> getCDOMReference(Class<T> c,
String val)
{
return getManufacturer(c).getReference(val);
}
public <T extends Categorized<T>> CDOMSingleRef<T> getCDOMReference(
Class<T> c, Category<T> cat, String val)
{
return getManufacturer(c, cat).getReference(val);
}
public <T extends Loadable> void reassociateKey(String key, T obj)
{
if (CATEGORIZED_CLASS.isAssignableFrom(obj.getClass()))
{
Class cl = obj.getClass();
reassociateCategorizedKey(key, obj, cl);
}
else
{
getManufacturer((Class<T>) obj.getClass()).renameObject(key, obj);
}
}
private <T extends Categorized<T>> void reassociateCategorizedKey(
String key, Loadable orig, Class<T> cl)
{
T obj = (T) orig;
getManufacturer(cl, obj.getCDOMCategory()).renameObject(key, obj);
}
@Override
public <T extends Loadable> T get(Class<T> c, String val)
{
return silentlyGetConstructedCDOMObject(c, val);
}
@Override
public <T extends Loadable> Indirect<T> getIndirect(Class<T> c, String val)
{
return getCDOMReference(c, val);
}
public <T extends Loadable> T silentlyGetConstructedCDOMObject(Class<T> c,
String val)
{
return getManufacturer(c).getActiveObject(val);
}
public <T extends Categorized<T>> T silentlyGetConstructedCDOMObject(
Class<T> c, Category<T> cat, String val)
{
return getManufacturer(c, cat).getActiveObject(val);
}
public <T extends Categorized<T>> void reassociateCategory(Category<T> cat,
T obj)
{
Category<T> oldCat = obj.getCDOMCategory();
if (oldCat == null && cat == null || oldCat != null
&& oldCat.equals(cat))
{
Logging.errorPrint("Worthless Category change encountered: "
+ obj.getDisplayName() + " " + oldCat);
}
reassociateCategory(getGenericClass(obj), obj, oldCat, cat);
}
@SuppressWarnings("unchecked")
protected <T> Class<T> getGenericClass(T obj)
{
return (Class<T>) obj.getClass();
}
private <T extends Categorized<T>> void reassociateCategory(
Class<T> cl, T obj, Category<T> oldCat, Category<T> cat)
{
getManufacturer(cl, oldCat).forgetObject(obj);
obj.setCDOMCategory(cat);
getManufacturer(cl, cat).addObject(obj, obj.getKeyName());
}
public <T extends Loadable> void importObject(T orig)
{
if (CATEGORIZED_CLASS.isAssignableFrom(orig.getClass()))
{
Class cl = orig.getClass();
importCategorized(orig, cl);
}
else
{
getManufacturer((Class<T>) orig.getClass()).addObject(orig,
orig.getKeyName());
}
}
private <T extends Categorized<T>> void importCategorized(Loadable orig,
Class<T> cl)
{
T obj = (T) orig;
getManufacturer(cl, obj.getCDOMCategory()).addObject(obj,
obj.getKeyName());
}
public <T extends Loadable> boolean forget(T obj)
{
if (CATEGORIZED_CLASS.isAssignableFrom(obj.getClass()))
{
Class cl = obj.getClass();
Categorized cdo = (Categorized) obj;
if (hasManufacturer(cl, cdo.getCDOMCategory()))
{
// Work around a bug in the Eclipse 3.7.0/1 compiler by explicitly extracting a Category<?>
return getManufacturer(cl, (Category<?>) cdo.getCDOMCategory()).forgetObject(obj);
}
}
else
{
if (hasManufacturer(obj.getClass()))
{
return getManufacturer((Class<T>) obj.getClass()).forgetObject(
obj);
}
}
return false;
}
public <T extends Loadable> Collection<T> getConstructedCDOMObjects(
Class<T> c)
{
// if (CategorizedCDOMObject.class.isAssignableFrom(c))
// {
// return categorized.getAllConstructedCDOMObjects((Class) c);
// }
// else
// {
return getManufacturer(c).getAllObjects();
// }
}
public <T extends Loadable> List<T> getOrderSortedCDOMObjects(Class<T> c)
{
return getManufacturer(c).getOrderSortedObjects();
}
public Set<Object> getAllConstructedObjects()
{
Set<Object> set = new HashSet<>();
for (ReferenceManufacturer<?> ref : getAllManufacturers())
{
set.addAll(ref.getAllObjects());
}
// Collection otherSet = categorized.getAllConstructedCDOMObjects();
// set.addAll(otherSet);
return set;
}
public <T extends Loadable> boolean containsConstructedCDOMObject(
Class<T> c, String s)
{
return getManufacturer(c).containsObject(s);
}
public void buildDerivedObjects()
{
Collection<Domain> domains = getConstructedCDOMObjects(Domain.class);
for (Domain d : domains)
{
DomainSpellList dsl = constructCDOMObject(DOMAINSPELLLIST_CLASS, d.getKeyName());
dsl.addType(Type.DIVINE);
d.put(ObjectKey.DOMAIN_SPELLLIST, dsl);
}
Collection<PCClass> classes = getConstructedCDOMObjects(PCClass.class);
for (PCClass pcc : classes)
{
String key = pcc.getKeyName();
ClassSkillList skl = constructCDOMObject(CLASSSKILLLIST_CLASS, key);
boolean isMonster = pcc.isMonster();
if (isMonster)
{
skl.addType(Type.MONSTER);
}
pcc.put(ObjectKey.CLASS_SKILLLIST, skl);
/*
* TODO Need to limit which are built to only spellcasters... If you
* do that, please see TO-DO in SpellListFacet
*/
ClassSpellList csl = constructCDOMObject(CLASSSPELLLIST_CLASS, key);
FactKey<String> fk = FactKey.valueOf("SpellType");
String spelltype = pcc.getResolved(fk);
if (spelltype != null)
{
csl.addType(Type.getConstant(spelltype));
}
pcc.put(ObjectKey.CLASS_SPELLLIST, csl);
// simple.constructCDOMObject(SPELLPROGRESSION_CLASS, key);
// Collection<CDOMSubClass> subclasses = categorized
// .getConstructedCDOMObjects(SUBCLASS_CLASS, SubClassCategory
// .getConstant(key));
// for (CDOMSubClass subcl : subclasses)
if (pcc.containsListFor(ListKey.SUB_CLASS))
{
SubClassCategory cat = SubClassCategory.getConstant(key);
boolean needSelf = pcc.getSafe(ObjectKey.ALLOWBASECLASS).booleanValue();
for (SubClass subcl : pcc.getListFor(ListKey.SUB_CLASS))
{
String subKey = subcl.getKeyName();
if (subKey.equalsIgnoreCase(key))
{
//Now an error to explicitly create this match, see CODE-1928
Logging.errorPrint("Cannot explicitly create a SUBCLASS that matches the parent class. "
+ "Use ALLOWBASECLASS. "
+ "Tokens on the offending SUBCLASS line will be ignored");
pcc.removeFromListFor(ListKey.SUB_CLASS, subcl);
continue;
}
skl = constructCDOMObject(CLASSSKILLLIST_CLASS, subKey);
if (isMonster)
{
skl.addType(Type.MONSTER);
}
subcl.put(ObjectKey.CLASS_SKILLLIST, skl);
// TODO Need to limit which are built to only
// spellcasters...
csl = constructCDOMObject(CLASSSPELLLIST_CLASS, subKey);
if (spelltype != null)
{
csl.addType(Type.getConstant(spelltype));
}
subcl.put(ObjectKey.CLASS_SPELLLIST, csl);
// constructCDOMObject(SPELLPROGRESSION_CLASS, subKey);
/*
* CONSIDER For right now, this is easiest to do here, though
* doing this 'live' may be more appropriate in the end.
*/
subcl.setCDOMCategory(cat);
importObject(subcl);
}
if (needSelf)
{
SubClass self = constructCDOMObject(SUBCLASS_CLASS, key);
reassociateCategory(SUBCLASS_CLASS, self, null, cat);
}
}
}
}
public <T extends CDOMObject> CDOMSingleRef<T> getCDOMDirectReference(T obj)
{
@SuppressWarnings("unchecked")
CDOMSingleRef<T> ref = (CDOMSingleRef<T>) directRefCache.get(obj);
if (ref == null)
{
ref = new CDOMDirectSingleRef<>(obj);
}
return ref;
}
URI getExtractURI()
{
return extractURI;
}
void setExtractURI(URI extractURI)
{
this.extractURI = extractURI;
}
URI getSourceURI()
{
return sourceURI;
}
void setSourceURI(URI sourceURI)
{
this.sourceURI = sourceURI;
}
public boolean resolveReferences(UnconstructedValidator validator)
{
boolean returnGood = true;
for (ReferenceManufacturer<?> rs : getAllManufacturers())
{
returnGood &= processResolution(validator, rs);
}
return returnGood;
}
private <T extends Loadable> boolean processResolution(
UnconstructedValidator validator, ReferenceManufacturer<T> rs)
{
ManufacturableFactory<T> factory = rs.getFactory();
ManufacturableFactory<T> parent = factory.getParent();
ReferenceManufacturer<T> manufacturer = (parent == null) ? null
: getManufacturer(parent);
return factory.populate(manufacturer, rs, validator)
&& rs.resolveReferences(validator);
}
public void buildDeferredObjects()
{
for (ReferenceManufacturer<?> rs : getAllManufacturers())
{
rs.buildDeferredObjects();
}
}
public <T extends Loadable> T constructNowIfNecessary(Class<T> cl, String name)
{
return getManufacturer(cl).constructNowIfNecessary(name);
}
public <T extends Loadable> int getConstructedObjectCount(Class<T> c)
{
return getManufacturer(c).getConstructedObjectCount();
}
public <T extends Loadable> T getItemInOrder(Class<T> cl, int item)
{
return getManufacturer(cl).getItemInOrder(item);
}
public <T extends Loadable> ReferenceManufacturer<T> getManufacturer(
ClassIdentity<T> identity)
{
Class cl = identity.getChoiceClass();
if (Categorized.class.isAssignableFrom(cl))
{
//Do categorized.
Category category = ((CategorizedClassIdentity) identity).getCategory();
return getManufacturer(cl, category);
}
else
{
return getManufacturer(cl);
}
}
public <T extends CDOMObject> List<T> getSortedList(Class<T> cl,
IntegerKey key)
{
List<T> returnList;
WeakReference<List<?>> wr = sortedMap.get(cl, key);
if ((wr == null) || ((returnList = (List<T>) wr.get()) == null))
{
returnList = generateList(cl, new IntegerKeyComparator(key));
sortedMap.put(cl, key, new WeakReference<>(returnList));
}
return Collections.unmodifiableList(returnList);
}
public <T extends CDOMObject> List<T> getSortOrderedList(Class<T> cl)
{
List<T> returnList;
Comparator<CDOMObject> comp = Globals.pObjectNameComp;
//We arbitrarily use the sort order comparator as the second key
WeakReference<List<?>> wr = sortedMap.get(cl, comp);
if ((wr == null) || ((returnList = (List<T>) wr.get()) == null))
{
returnList = generateList(cl, comp);
sortedMap.put(cl, comp, new WeakReference<>(returnList));
}
return Collections.unmodifiableList(returnList);
}
private <T extends CDOMObject> List<T> generateList(Class<T> cl,
Comparator<? super T> comp)
{
Set<T> tm = new TreeSet<>(comp);
tm.addAll(getConstructedCDOMObjects(cl));
return new ArrayList<>(tm);
}
public abstract <T extends Categorized<T>> ReferenceManufacturer<T> getManufacturer(
Class<T> cl, Category<T> cat);
public abstract <T extends Loadable> ReferenceManufacturer<T> getManufacturer(
ManufacturableFactory<T> factory);
public abstract <T extends Categorized<T>> ReferenceManufacturer<T> getManufacturer(
Class<T> cl, Class<? extends Category<T>> catClass, String category);
abstract <T extends CDOMObject> T performCopy(T object, String copyName);
public abstract <T extends CDOMObject> T performMod(T obj);
public FormatManager<?> getFormatManager(String clName)
{
return fmtLibrary.getFormatManager(clName);
}
}