/* * 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.context; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Set; import java.util.TreeSet; import pcgen.base.util.DoubleKeyMap; import pcgen.base.util.HashMapToList; import pcgen.base.util.ListSet; import pcgen.base.util.MapToList; import pcgen.base.util.TreeMapToList; import pcgen.base.util.TripleKeyMap; import pcgen.base.util.TripleKeyMapToList; import pcgen.cdom.base.AssociatedPrereqObject; import pcgen.cdom.base.CDOMList; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.base.CDOMObjectUtilities; import pcgen.cdom.base.CDOMReference; import pcgen.cdom.base.MasterListInterface; import pcgen.cdom.base.SimpleAssociatedObject; import pcgen.cdom.enumeration.AssociationKey; import pcgen.cdom.reference.ReferenceUtilities; import pcgen.core.SettingsHandler; public abstract class AbstractListContext { private final TrackingListCommitStrategy edits = new TrackingListCommitStrategy(); URI getSourceURI() { return edits.getSourceURI(); } void setSourceURI(URI sourceURI) { edits.setSourceURI(sourceURI); getCommitStrategy().setSourceURI(sourceURI); } URI getExtractURI() { return edits.getExtractURI(); } void setExtractURI(URI extractURI) { edits.setExtractURI(extractURI); getCommitStrategy().setExtractURI(extractURI); } public <T extends CDOMObject> AssociatedPrereqObject addToMasterList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> list, T allowed) { return edits.addToMasterList(tokenName, owner, list, allowed); } public <T extends CDOMObject> void removeFromMasterList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> list, T allowed) { edits.removeFromMasterList(tokenName, owner, list, allowed); } public void clearAllMasterLists(String tokenName, CDOMObject owner) { edits.clearAllMasterLists(tokenName, owner); } public <T extends CDOMObject> void clearMasterList(String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> list) { edits.clearMasterList(tokenName, owner, list); } public <T extends CDOMObject> AssociatedPrereqObject addToList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<? super T>> list, CDOMReference<T> allowed) { return edits.addToList(tokenName, owner, list, allowed); } public <T extends CDOMObject> AssociatedPrereqObject removeFromList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<? super T>> list, CDOMReference<T> ref) { return edits.removeFromList(tokenName, owner, list, ref); } public void removeAllFromList(String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<?>> swl) { edits.removeAllFromList(tokenName, owner, swl); } void commit() { ListCommitStrategy commit = getCommitStrategy(); for (CDOMReference<? extends CDOMList<?>> list : edits.positiveMasterMap.getKeySet()) { //Note: Intentional Generics Violation due to Sun Compiler commitDirect((CDOMReference) list); } for (CDOMReference<? extends CDOMList<?>> list : edits.negativeMasterMap.getKeySet()) { //Note: Intentional Generics Violation due to Sun Compiler removeDirect((CDOMReference) list); } for (URI uri : edits.globalClearSet.getKeySet()) { for (CDOMObject owner : edits.globalClearSet .getSecondaryKeySet(uri)) { for (String tokenName : edits.globalClearSet.getTertiaryKeySet( uri, owner)) { for (CDOMReference<? extends CDOMList<?>> list : edits.globalClearSet .getListFor(uri, owner, tokenName)) { commit.removeAllFromList(tokenName, owner, list); } } } } for (URI uri : edits.negativeMap.getKeySet()) { for (CDOMObject owner : edits.negativeMap.getSecondaryKeySet(uri)) { CDOMObject neg = edits.negativeMap.get(uri, owner); Collection<CDOMReference<? extends CDOMList<?>>> modifiedLists = neg.getModifiedLists(); for (CDOMReference<? extends CDOMList<?>> list : modifiedLists) { //Note: Intentional Generics Violation due to Sun Compiler remove(owner, neg, (CDOMReference) list); } } } for (URI uri : edits.positiveMap.getKeySet()) { for (CDOMObject owner : edits.positiveMap.getSecondaryKeySet(uri)) { CDOMObject neg = edits.positiveMap.get(uri, owner); Collection<CDOMReference<? extends CDOMList<?>>> modifiedLists = neg .getModifiedLists(); for (CDOMReference<? extends CDOMList<?>> list : modifiedLists) { //Note: Intentional Generics Violation due to Sun Compiler add(owner, neg, (CDOMReference) list); } } } for (String token : edits.masterAllClear.getKeySet()) { for (OwnerURI ou : edits.masterAllClear.getListFor(token)) { commit.clearAllMasterLists(token, ou.owner); } } rollback(); } private <T extends CDOMObject, L extends CDOMList<T>> void commitDirect( CDOMReference<L> list) { ListCommitStrategy commit = getCommitStrategy(); for (OwnerURI ou : edits.positiveMasterMap.getSecondaryKeySet(list)) { for (CDOMObject child : edits.positiveMasterMap.getTertiaryKeySet( list, ou)) { AssociatedPrereqObject assoc = edits.positiveMasterMap.get( list, ou, child); AssociatedPrereqObject edge = commit.addToMasterList(assoc .getAssociation(AssociationKey.TOKEN), ou.owner, list, (T) child); Collection<AssociationKey<?>> associationKeys = assoc .getAssociationKeys(); for (AssociationKey<?> ak : associationKeys) { setAssoc(assoc, edge, ak); } edge.addAllPrerequisites(assoc.getPrerequisiteList()); } } } private <T extends CDOMObject, U extends CDOMList<T>> void removeDirect( CDOMReference<U> list) { ListCommitStrategy commit = getCommitStrategy(); for (OwnerURI ou : edits.negativeMasterMap.getSecondaryKeySet(list)) { for (CDOMObject child : edits.negativeMasterMap.getTertiaryKeySet( list, ou)) { AssociatedPrereqObject assoc = edits.negativeMasterMap.get( list, ou, child); commit.removeFromMasterList(assoc .getAssociation(AssociationKey.TOKEN), ou.owner, list, (T) child); } } } void rollback() { edits.decommit(); } public Collection<CDOMReference<? extends CDOMList<?>>> getChangedLists( CDOMObject owner, Class<? extends CDOMList<?>> cl) { return getCommitStrategy().getChangedLists(owner, cl); } public <T extends CDOMObject> AssociatedChanges<CDOMReference<T>> getChangesInList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> swl) { return getCommitStrategy().getChangesInList(tokenName, owner, swl); } public <T extends CDOMObject> AssociatedChanges<T> getChangesInMasterList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> swl) { return getCommitStrategy().getChangesInMasterList(tokenName, owner, swl); } public <T extends CDOMList<?>> Changes<CDOMReference<T>> getMasterListChanges(String tokenName, CDOMObject owner, Class<T> cl) { return getCommitStrategy().getMasterListChanges(tokenName, owner, cl); } public boolean hasMasterLists() { return getCommitStrategy().hasMasterLists(); } private <BT extends CDOMObject, L extends CDOMList<BT>> void remove(CDOMObject owner, CDOMObject neg, CDOMReference<L> list) { ListCommitStrategy commit = getCommitStrategy(); Collection<CDOMReference<BT>> mods = neg.getListMods(list); for (CDOMReference<BT> ref : mods) { for (AssociatedPrereqObject assoc : neg.getListAssociations(list, ref)) { String token = assoc.getAssociation(AssociationKey.TOKEN); AssociatedPrereqObject edge = commit.removeFromList(token, owner, list, ref); Collection<AssociationKey<?>> associationKeys = assoc .getAssociationKeys(); for (AssociationKey<?> ak : associationKeys) { setAssoc(assoc, edge, ak); } edge.addAllPrerequisites(assoc.getPrerequisiteList()); } } } private <BT extends CDOMObject, L extends CDOMList<BT>> void add( CDOMObject owner, CDOMObject neg, CDOMReference<L> list) { ListCommitStrategy commit = getCommitStrategy(); Collection<CDOMReference<BT>> mods = neg.getListMods(list); for (CDOMReference<BT> ref : mods) { for (AssociatedPrereqObject assoc : neg.getListAssociations(list, ref)) { String token = assoc.getAssociation(AssociationKey.TOKEN); AssociatedPrereqObject edge = commit.addToList(token, owner, list, ref); Collection<AssociationKey<?>> associationKeys = assoc .getAssociationKeys(); for (AssociationKey<?> ak : associationKeys) { setAssoc(assoc, edge, ak); } edge.addAllPrerequisites(assoc.getPrerequisiteList()); } } } private <T> void setAssoc(AssociatedPrereqObject assoc, AssociatedPrereqObject edge, AssociationKey<T> ak) { edge.setAssociation(ak, assoc.getAssociation(ak)); } public static class TrackingListCommitStrategy implements ListCommitStrategy { private final DoubleKeyMap<URI, CDOMObject, CDOMObject> positiveMap = new DoubleKeyMap<>(HashMap.class, IdentityHashMap.class); private final DoubleKeyMap<URI, CDOMObject, CDOMObject> negativeMap = new DoubleKeyMap<>(HashMap.class, IdentityHashMap.class); private final TripleKeyMapToList<URI, CDOMObject, String, CDOMReference<? extends CDOMList<?>>> globalClearSet = new TripleKeyMapToList<>( HashMap.class, IdentityHashMap.class, HashMap.class); /* * TODO These maps (throughout this entire class) are probably problems * because they are not using Identity characteristics */ private final TripleKeyMap<CDOMReference<? extends CDOMList<?>>, OwnerURI, CDOMObject, AssociatedPrereqObject> positiveMasterMap = new TripleKeyMap<>(); //HashMap.class, HashMap.class, IdentityHashMap.class); private final TripleKeyMap<CDOMReference<? extends CDOMList<?>>, OwnerURI, CDOMObject, AssociatedPrereqObject> negativeMasterMap = new TripleKeyMap<>(); //HashMap.class, HashMap.class, IdentityHashMap.class); private final HashMapToList<CDOMReference<? extends CDOMList<?>>, OwnerURI> masterClearSet = new HashMapToList<>(); private final HashMapToList<String, OwnerURI> masterAllClear = new HashMapToList<>(); private URI sourceURI; private URI extractURI; protected static class CDOMShell extends CDOMObject { @Override public CDOMObject clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); } @Override public boolean isType(String str) { return false; } } public URI getExtractURI() { return extractURI; } @Override public void setExtractURI(URI extractURI) { this.extractURI = extractURI; } public URI getSourceURI() { return sourceURI; } @Override public void setSourceURI(URI sourceURI) { this.sourceURI = sourceURI; } @Override public <T extends CDOMObject> AssociatedPrereqObject addToMasterList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> list, T allowed) { SimpleAssociatedObject a = new SimpleAssociatedObject(); a.setAssociation(AssociationKey.OWNER, owner); a.setAssociation(AssociationKey.TOKEN, tokenName); positiveMasterMap.put(list, new OwnerURI(sourceURI, owner), allowed, a); return a; } @Override public <T extends CDOMObject> void removeFromMasterList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> list, T allowed) { SimpleAssociatedObject a = new SimpleAssociatedObject(); a.setAssociation(AssociationKey.OWNER, owner); a.setAssociation(AssociationKey.TOKEN, tokenName); negativeMasterMap.put(list, new OwnerURI(sourceURI, owner), allowed, a); } @Override public <T extends CDOMList<?>> Changes<CDOMReference<T>> getMasterListChanges(String tokenName, CDOMObject owner, Class<T> cl) { OwnerURI lo = new OwnerURI(extractURI, owner); TreeSet<CDOMReference<T>> list = new TreeSet<>(ReferenceUtilities.REFERENCE_SORTER); Set<CDOMReference<? extends CDOMList<?>>> set = positiveMasterMap.getKeySet(); if (set != null) { LIST: for (CDOMReference<? extends CDOMList<?>> ref : set) { if (!cl.equals(ref.getReferenceClass())) { continue; } @SuppressWarnings("unchecked") CDOMReference<T> tr = (CDOMReference<T>) ref; for (CDOMObject allowed : positiveMasterMap .getTertiaryKeySet(tr, lo)) { AssociatedPrereqObject assoc = positiveMasterMap.get( tr, lo, allowed); if (owner.equals(assoc .getAssociation(AssociationKey.OWNER)) && tokenName.equals(assoc .getAssociation(AssociationKey.TOKEN))) { list.add(tr); continue LIST; } } } } set = negativeMasterMap.getKeySet(); ArrayList<CDOMReference<T>> removelist = new ArrayList<>(); if (set != null) { LIST: for (CDOMReference<? extends CDOMList<?>> ref : set) { if (!cl.equals(ref.getReferenceClass())) { continue; } @SuppressWarnings("unchecked") CDOMReference<T> tr = (CDOMReference<T>) ref; for (CDOMObject allowed : negativeMasterMap .getTertiaryKeySet(tr, lo)) { AssociatedPrereqObject assoc = negativeMasterMap.get( tr, lo, allowed); if (owner.equals(assoc .getAssociation(AssociationKey.OWNER)) && tokenName.equals(assoc .getAssociation(AssociationKey.TOKEN))) { removelist.add(tr); continue LIST; } } } } return new CollectionChanges<>(list, removelist, masterAllClear.containsInList(tokenName, lo)); } @Override public <T extends CDOMObject> AssociatedChanges<T> getChangesInMasterList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> swl) { MapToList<T, AssociatedPrereqObject> map = new TreeMapToList<>( CDOMObjectUtilities.CDOM_SORTER); OwnerURI lo = new OwnerURI(extractURI, owner); Set<CDOMObject> added = positiveMasterMap .getTertiaryKeySet(swl, lo); for (CDOMObject lw : added) { AssociatedPrereqObject apo = positiveMasterMap.get(swl, lo, lw); if (tokenName.equals(apo.getAssociation(AssociationKey.TOKEN))) { map.addToListFor((T) lw, apo); } } MapToList<T, AssociatedPrereqObject> rmap = new TreeMapToList<>( CDOMObjectUtilities.CDOM_SORTER); Set<CDOMObject> removed = negativeMasterMap .getTertiaryKeySet(swl, lo); for (CDOMObject lw : removed) { AssociatedPrereqObject apo = negativeMasterMap.get(swl, lo, lw); if (tokenName.equals(apo.getAssociation(AssociationKey.TOKEN))) { rmap.addToListFor((T) lw, apo); } } return new AssociatedCollectionChanges<>(map, rmap, masterClearSet .containsInList(swl, lo)); } public <T extends CDOMObject> void clearMasterList(String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> list) { masterClearSet.addToListFor(list, new OwnerURI(sourceURI, owner)); } @Override public void clearAllMasterLists(String tokenName, CDOMObject owner) { masterAllClear.addToListFor(tokenName, new OwnerURI(sourceURI, owner)); } private CDOMObject getPositive(URI source, CDOMObject cdo) { CDOMObject positive = positiveMap.get(source, cdo); if (positive == null) { positive = new CDOMShell(); positiveMap.put(source, cdo, positive); } return positive; } private CDOMObject getNegative(URI source, CDOMObject cdo) { CDOMObject negative = negativeMap.get(source, cdo); if (negative == null) { negative = new CDOMShell(); negativeMap.put(source, cdo, negative); } return negative; } @Override public <T extends CDOMObject> AssociatedPrereqObject addToList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<? super T>> list, CDOMReference<T> allowed) { SimpleAssociatedObject a = new SimpleAssociatedObject(); a.setAssociation(AssociationKey.TOKEN, tokenName); getPositive(sourceURI, owner).putToList(list, allowed, a); return a; } @Override public <T extends CDOMObject> AssociatedPrereqObject removeFromList(String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<? super T>> list, CDOMReference<T> ref) { SimpleAssociatedObject a = new SimpleAssociatedObject(); a.setAssociation(AssociationKey.TOKEN, tokenName); getNegative(sourceURI, owner).putToList(list, ref, a); return a; } @Override public Collection<CDOMReference<? extends CDOMList<?>>> getChangedLists( CDOMObject owner, Class<? extends CDOMList<?>> cl) { Set<CDOMReference<? extends CDOMList<?>>> list = new ListSet<>(); for (CDOMReference<? extends CDOMList<?>> ref : getPositive( extractURI, owner).getModifiedLists()) { if (cl.equals(ref.getReferenceClass())) { list.add(ref); } } for (CDOMReference<? extends CDOMList<?>> ref : getNegative( extractURI, owner).getModifiedLists()) { if (cl.equals(ref.getReferenceClass())) { list.add(ref); } } Set<String> globalClearTokenKeys = globalClearSet.getTertiaryKeySet(extractURI, owner); for (String key : globalClearTokenKeys) { List<CDOMReference<? extends CDOMList<?>>> globalClearList = globalClearSet .getListFor(extractURI, owner, key); if (globalClearList != null) { for (CDOMReference<? extends CDOMList<?>> ref : globalClearList) { if (cl.equals(ref.getReferenceClass())) { list.add(ref); } } } } return list; } @Override public void removeAllFromList(String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<?>> swl) { globalClearSet.addToListFor(sourceURI, owner, tokenName, swl); } @Override public <T extends CDOMObject> AssociatedChanges<CDOMReference<T>> getChangesInList( String tokenName, CDOMObject owner, CDOMReference<? extends CDOMList<T>> swl) { boolean hasGlobalClear = globalClearSet .containsListFor(extractURI, owner, tokenName) && globalClearSet.getListFor(extractURI, owner, tokenName).contains(swl); return new ListChanges<>(tokenName, getPositive(extractURI, owner), getNegative(extractURI, owner), swl, hasGlobalClear); } @Override public boolean hasMasterLists() { return !positiveMasterMap.isEmpty() && !masterClearSet.isEmpty() && !masterAllClear.isEmpty(); } public void decommit() { masterAllClear.clear(); masterClearSet.clear(); positiveMasterMap.clear(); negativeMasterMap.clear(); positiveMap.clear(); negativeMap.clear(); globalClearSet.clear(); } @Override public boolean equalsTracking(ListCommitStrategy obj) { if (obj instanceof TrackingListCommitStrategy) { TrackingListCommitStrategy other = (TrackingListCommitStrategy) obj; return other.masterAllClear.equals(this.masterAllClear) && other.masterClearSet.equals(this.masterClearSet) && other.positiveMasterMap.equals(this.positiveMasterMap) && other.negativeMasterMap.equals(this.negativeMasterMap); } return false; } public void purge(CDOMObject cdo) { positiveMap.remove(sourceURI, cdo); negativeMap.remove(sourceURI, cdo); globalClearSet.removeListsFor(sourceURI, cdo); } } private static class OwnerURI { public final CDOMObject owner; public final URI source; public OwnerURI(URI sourceURI, CDOMObject cdo) { source = sourceURI; owner = cdo; } @Override public int hashCode() { return owner.hashCode(); } @Override public boolean equals(Object o) { if (o instanceof OwnerURI) { OwnerURI other = (OwnerURI) o; if (source == null) { if (other.source != null) { return false; } } else { if (!source.equals(other.source)) { return false; } } return owner.equals(other.owner); } return false; } } public boolean masterListsEqual(AbstractListContext lc) { return getCommitStrategy().equalsTracking(lc.getCommitStrategy()); } protected abstract ListCommitStrategy getCommitStrategy(); /** * Create a copy of any associations to the original object and link them * to the new object. This will scan lists such as ClassSpellLists and * DomainSpellLists which may link to the original object. For each * association found, a new association will be created linking to the new object * and the association will be added to the list. * * @param <T> The type of CDOMObject being copied (e.g. Spell, Domain etc) * @param cdoOld The original object being copied. * @param cdoNew The new object to be linked in. */ @SuppressWarnings("unchecked") <T extends CDOMObject> void cloneInMasterLists(T cdoOld, T cdoNew) { MasterListInterface masterLists = SettingsHandler.getGame().getMasterLists(); for (CDOMReference ref : masterLists.getActiveLists()) { Collection<AssociatedPrereqObject> assocs = masterLists .getAssociations(ref, cdoOld); if (assocs != null) { for (AssociatedPrereqObject apo : assocs) { // Logging.debugPrint("Found assoc from " + ref + " to " // + apo.getAssociationKeys() + " / " // + apo.getAssociation(AssociationKey.OWNER)); AssociatedPrereqObject newapo = getCommitStrategy() .addToMasterList( apo.getAssociation(AssociationKey.TOKEN), cdoNew, ref, cdoNew); newapo.addAllPrerequisites(apo.getPrerequisiteList()); for (AssociationKey assocKey : apo.getAssociationKeys()) { if (assocKey != AssociationKey.TOKEN && assocKey != AssociationKey.OWNER) { newapo.setAssociation(assocKey, apo .getAssociation(assocKey)); } } } } } } }