@Override protected void setUp() throws Exception { super.setUp(); knownSpellFacet = FacetLibrary.getFacet(KnownSpellFacet.class); wizardSpellList = context.ref.constructNowIfNecessary(ClassSpellList.class, "Wizard"); fb = context.ref.constructNowIfNecessary(Spell.class, "Fireball"); TokenRegistration.register(new PreVariableParser()); classSetUp(); }
/** A CNAbilitySelectionModel is a TemplateHashModel that wraps a CNAbilitySelection object */ public class CNAbilitySelectionModel implements TemplateHashModel { private static final ObjectWrapperFacet WRAPPER_FACET = FacetLibrary.getFacet(ObjectWrapperFacet.class); /** The underlying CNAbilitySelection for this CNAbilitySelectionModel */ private final CNAbilitySelection cnas; private final CharID id; /** * Constructs a new CNAbilitySelectionModel with the given CNAbilitySelection as the underlying * information. * * @param cnas The CNAbilitySelection that underlies this CNAbilitySelectionModel */ public CNAbilitySelectionModel(CharID id, CNAbilitySelection cnas) { if (id == null) { throw new IllegalArgumentException("CharID cannot be null"); } if (cnas == null) { throw new IllegalArgumentException("CNAbilitySelection cannot be null"); } this.id = id; this.cnas = cnas; } /** * Acts as a hash for producing the contents of this model. * * <p>Four items are supported: category, nature, ability and selection. * * @see freemarker.template.TemplateHashModel#get(java.lang.String) */ @Override public TemplateModel get(String key) throws TemplateModelException { Object towrap; if ("category".equals(key)) { towrap = cnas.getCNAbility().getAbilityCategory(); } else if ("nature".equals(key)) { towrap = cnas.getCNAbility().getNature(); } else if ("ability".equals(key)) { towrap = cnas.getCNAbility().getAbility(); } else if ("selection".equals(key)) { towrap = cnas.getSelection(); } else { throw new TemplateModelException("CNAbilitySelection did not have output of type " + key); } return WRAPPER_FACET.wrap(id, towrap); } /** @see freemarker.template.TemplateHashModel#isEmpty() */ @Override public boolean isEmpty() throws TemplateModelException { return false; } }
@Override protected void setUp() throws Exception { super.setUp(); lscFacet = FacetLibrary.getFacet(ListSkillCostFacet.class); sk = context.ref.constructCDOMObject(Skill.class, "MySkill"); dragon = context.ref.constructCDOMObject(PCClass.class, "Dragon"); dragon.addToListFor(ListKey.TYPE, Type.MONSTER); dragon.put(ObjectKey.IS_MONSTER, Boolean.TRUE); TokenRegistration.register(CHOOSE_SKILL_TOKEN); ChooserFactory.setDelegate(new MockUIDelegate()); }
public class RaceMonCCSkillTest extends AbstractTokenModelTest { private static MonccskillToken token = new MonccskillToken(); private static SkillToken CHOOSE_SKILL_TOKEN = new SkillToken(); protected RaceInputFacet raceInputFacet = FacetLibrary.getFacet(RaceInputFacet.class); private ListSkillCostFacet lscFacet; private Skill sk; private PCClass dragon; @Override protected void setUp() throws Exception { super.setUp(); lscFacet = FacetLibrary.getFacet(ListSkillCostFacet.class); sk = context.ref.constructCDOMObject(Skill.class, "MySkill"); dragon = context.ref.constructCDOMObject(PCClass.class, "Dragon"); dragon.addToListFor(ListKey.TYPE, Type.MONSTER); dragon.put(ObjectKey.IS_MONSTER, Boolean.TRUE); TokenRegistration.register(CHOOSE_SKILL_TOKEN); ChooserFactory.setDelegate(new MockUIDelegate()); } @Test public void testDirect() throws PersistenceLayerException { Race source = create(Race.class, "Source"); ParseResult result = token.parseToken(context, source, "MySkill"); if (result != ParseResult.SUCCESS) { result.printMessages(); fail("Test Setup Failed"); } new ExclusiveToken().parseToken(context, sk, "Yes"); finishLoad(); assertEquals(SkillCost.EXCLUSIVE, pc.getSkillCostForClass(sk, dragon)); raceFacet.directSet(id, source, getAssoc()); ClassSkillList dragonCSL = context.ref.silentlyGetConstructedCDOMObject(ClassSkillList.class, "Dragon"); assertEquals(SkillCost.EXCLUSIVE, pc.getSkillCostForClass(sk, dragon)); pc.incrementClassLevel(1, dragon); assertEquals(SkillCost.CROSS_CLASS, pc.getSkillCostForClass(sk, dragon)); assertTrue(lscFacet.contains(id, dragonCSL, SkillCost.CROSS_CLASS, sk)); raceFacet.remove(id); assertFalse(lscFacet.contains(id, dragonCSL, SkillCost.CROSS_CLASS, sk)); } @Override public CDOMToken<?> getToken() { return token; } }
public class SolverViewFrame extends JFrame { private static final ScopeFacet scopeFacet = FacetLibrary.getFacet(ScopeFacet.class); private static final VariableLibraryFacet variableLibraryFacet = FacetLibrary.getFacet(VariableLibraryFacet.class); private static final SolverManagerFacet solverManagerFacet = FacetLibrary.getFacet(SolverManagerFacet.class); private static final FormulaSetupFacet formulaSetupFacet = FacetLibrary.getFacet(FormulaSetupFacet.class); private JComboBoxEx scopeChooser; private LegalScope selectedScope; private JTextField varName; private String varNameText = " "; private JComboBoxEx objectChooser; private VarScoped activeObject; private JComboBoxEx identifierChooser; private CharID activeIdentifier; private JTable viewTable; private SolverTableModel tableModel; public SolverViewFrame(Frame frame) { identifierChooser = new JComboBoxEx(); for (CharacterFacade pcf : CharacterManager.getCharacters()) { String pcname = pcf.getNameRef().get(); CharID id = pcf.getCharID(); identifierChooser.addItem(new PCRef(pcname, id)); } identifierChooser.addActionListener(new IdentifierActionListener()); objectChooser = new JComboBoxEx(); objectChooser.addActionListener(new ObjectActionListener()); scopeChooser = new JComboBoxEx(); scopeChooser.addActionListener(new ScopeActionListener()); identifierChooser.setSelectedItem(identifierChooser.getItemAt(0)); scopeChooser.setSelectedItem(scopeChooser.getItemAt(0)); varName = new JTextField(); varName.setText(varNameText); varName.getDocument().addDocumentListener(new VarNameListener()); initialize(); objectChooser.setSelectedItem(objectChooser.getItemAt(0)); } private void update() { updateObjects(); ScopeInstance scope = scopeFacet.get(activeIdentifier, selectedScope, activeObject); if (variableLibraryFacet.isLegalVariableName( activeIdentifier.getDatasetID(), scope.getLegalScope(), varNameText)) { displayInfo(scope); } else { // TODO Update a status bar System.err.println(selectedScope.getName() + " does not have a variable: " + varNameText); } } private void displayInfo(ScopeInstance scope) { VariableID<?> varID = variableLibraryFacet.getVariableID(activeIdentifier.getDatasetID(), scope, varNameText); setSteps(varID); } private <T> void setSteps(VariableID<T> varID) { List<ProcessStep<T>> steps = solverManagerFacet.diagnose(activeIdentifier, varID); tableModel.setSteps(steps); } private class ScopeActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { LegalScopeWrapper wrap = (LegalScopeWrapper) scopeChooser.getSelectedItem(); selectedScope = wrap.getLegalScope(); update(); } } private class ObjectActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { ObjectNameDisplayer displayer = (ObjectNameDisplayer) objectChooser.getSelectedItem(); if (displayer == null) { activeObject = null; tableModel.setSteps(Collections.emptyList()); } else { activeObject = displayer.getObject(); update(); } } } private class VarNameListener implements DocumentListener { @Override public void insertUpdate(DocumentEvent e) { varNameText = varName.getText().trim(); update(); } @Override public void removeUpdate(DocumentEvent e) { varNameText = varName.getText().trim(); update(); } @Override public void changedUpdate(DocumentEvent e) { varNameText = varName.getText().trim(); update(); } } private class IdentifierActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { Object item = identifierChooser.getSelectedItem(); activeIdentifier = ((PCRef) item).id; SplitFormulaSetup formulaSetup = formulaSetupFacet.get(activeIdentifier.getDatasetID()); for (LegalScope lvs : formulaSetup.getLegalScopeLibrary().getLegalScopes()) { scopeChooser.addItem(new LegalScopeWrapper(lvs)); } update(); } } private void updateObjects() { if (activeIdentifier != null) { Collection<VarScoped> objects = scopeFacet.getObjectsWithVariables(activeIdentifier); objectChooser.removeAllItems(); String scopeName = selectedScope.getName(); for (VarScoped cdo : objects) { if (cdo.getLocalScopeName().equals(scopeName)) { if (scopeFacet.get(activeIdentifier, selectedScope, cdo) != null) { objectChooser.addItem(new ObjectNameDisplayer(cdo)); } } } if (objectChooser.getItemCount() != 0) { objectChooser.setSelectedIndex(0); } } } public void initialize() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); getContentPane().setLayout(gridbag); c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.NORTHWEST; c.insets = new Insets(2, 2, 2, 2); int col = 0; Utility.buildConstraints(c, col, 0, 1, 1, 100, 20); JLabel label = new JLabel(LanguageBundle.getFormattedString("in_SolverView_Perspective")); // $NON-NLS-1$ gridbag.setConstraints(label, c); getContentPane().add(label); Utility.buildConstraints(c, col++, 1, 1, 1, 0, 20); gridbag.setConstraints(identifierChooser, c); getContentPane().add(identifierChooser); Utility.buildConstraints(c, col++, 1, 1, 1, 0, 20); gridbag.setConstraints(scopeChooser, c); getContentPane().add(scopeChooser); Utility.buildConstraints(c, col++, 1, 1, 1, 0, 20); gridbag.setConstraints(objectChooser, c); getContentPane().add(objectChooser); Utility.buildConstraints(c, col++, 1, 1, 1, 0, 20); label = new JLabel( LanguageBundle.getFormattedString("in_SolverView_VarName") // $NON-NLS-1$ ); gridbag.setConstraints(label, c); getContentPane().add(label); Utility.buildConstraints(c, col++, 1, 1, 1, 0, 20); gridbag.setConstraints(varName, c); getContentPane().add(varName); tableModel = new SolverTableModel(); viewTable = new JTable(tableModel); viewTable.getColumnModel().getColumn(0).setPreferredWidth(25); viewTable.getColumnModel().getColumn(1).setPreferredWidth(50); viewTable.getColumnModel().getColumn(2).setPreferredWidth(25); viewTable.getColumnModel().getColumn(3).setPreferredWidth(50); Utility.buildConstraints(c, 0, 2, col, 1, 0, 1000); JScrollPane pane = new JScrollPane(viewTable); viewTable.setFillsViewportHeight(true); pane.setPreferredSize(new Dimension(500, 300)); gridbag.setConstraints(pane, c); getContentPane().add(pane); setTitle("Core Variable Debug View"); getContentPane().setSize(500, 400); pack(); Utility.centerFrame(this, true); } private static class SolverTableModel<T> extends AbstractTableModel { private String[] columnNames = { "Modification Type", "Modification", "Resulting Value", "Priority", "Source" }; private List<ProcessStep<T>> steps = Collections.emptyList(); @Override public String getColumnName(int column) { return columnNames[column]; } @Override public int getRowCount() { return steps.size(); } @Override public int getColumnCount() { return columnNames.length; } @Override public Object getValueAt(int rowIndex, int columnIndex) { ProcessStep<T> ps = steps.get(rowIndex); switch (columnIndex) { case 0: return ps.getModifier().getIdentification(); case 1: return ps.getModifier().getInstructions(); case 2: return ps.getResult(); case 3: return ps.getModifier().getUserPriority(); case 4: return ps.getSourceInfo(); default: return ""; } } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { return false; } public void setSteps(List<ProcessStep<T>> steps) { this.steps = steps; fireTableDataChanged(); } } private class PCRef { public String name; public CharID id; public PCRef(String pcname, CharID id) { this.name = pcname; this.id = id; } @Override public String toString() { return name; } } }
/** * UserSpecialAbilityFacet tracks the SpecialAbility objects added to a Player Character by explicit * user action (old UI capability). * * <p>TODO Need to consider whether this sticks around, or how we handle this capability in the new * UI (and transition any Player Characters that have a user SpecialAbility. * * @author Thomas Parker (thpr [at] yahoo.com) */ public class UserSpecialAbilityFacet extends AbstractQualifiedListFacet<SpecialAbility> { private final PlayerCharacterTrackingFacet trackingFacet = FacetLibrary.getFacet(PlayerCharacterTrackingFacet.class); /** * Returns a non-null copy of the List of resolved SpecialAbility objects for the given source * CDOMObject and the Player Character represented by the given CharID. The Player Character must * qualify for the Special Ability (if it has prerequisites) in order for the resolved * SpecialAbility to be returned by this method. This method returns an empty List if no * SpecialAbility objects are in this UserSpecialAbilityFacet for the given source CDOMObject and * the Player Character identified by the given CharID. * * <p>This method is value-semantic in that ownership of the returned List is transferred to the * class calling this method. Modification of the returned List will not modify this * UserSpecialAbilityFacet and modification of this UserSpecialAbilityFacet will not modify the * returned List. Modifications to the returned List will also not modify any future or previous * objects returned by this (or other) methods on UserSpecialAbilityFacet. If you wish to modify * the information stored in this UserSpecialAbilityFacet, you must use the add*() and remove*() * methods of UserSpecialAbilityFacet. * * @param id The CharID representing the Player Character for which a copy of the resolved items * in this UserSpecialAbilityFacet should be returned * @param source The source of the SpecialAbility objects for this UserSpecialAbilityFacet to be * used for the resolution of the SpecialAbility objects in the Player Character * @return A non-null List of resolved SpecialAbility objects from this UserSpecialAbilityFacet * for the Player Character represented by the given CharID */ public List<SpecialAbility> getResolved(CharID id, Object source) { List<SpecialAbility> returnList = new ArrayList<>(); SAProcessor proc = new SAProcessor(trackingFacet.getPC(id)); for (SpecialAbility sa : getQualifiedSet(id, source)) { returnList.add(proc.act(sa, source)); } return returnList; } /** * Returns a non-null List of processed SpecialAbility objects for the Player Character * represented by the given CharID. The given QualifiedActor will determine the type and contents * of the returned List. This method returns an empty List if no objects are in this * UserSpecialAbilityFacet for the Player Character identified by the given CharID. * * <p>This method is value-semantic in that ownership of the returned List is transferred to the * class calling this method. Modification of the returned List will not modify this * UserSpecialAbilityFacet and modification of this UserSpecialAbilityFacet will not modify the * returned List. Modifications to the returned List will also not modify any future or previous * objects returned by this (or other) methods on UserSpecialAbilityFacet. If you wish to modify * the information stored in this UserSpecialAbilityFacet, you must use the add*() and remove*() * methods of UserSpecialAbilityFacet. * * <p>Note: If a particular item has been granted by more than one source, then the QualifiedActor * will only be called for the first source that (successfully grants) the underlying object. * * @param <T> The type of objects returned by the given QualifiedActor (and thus also the type of * the List returned by this method) * @param id The CharID representing the Player Character for which the processed items in this * UserSpecialAbilityFacet should be returned * @param qa The QualifiedActor which will act on each of the items in this * UserSpecialAbilityFacet for which the Player Character qualifies. * @return A non-null List of objects created by the QualifiedActor from each of the objects in * this UserSpecialAbilityFacet for which the Player Character qualifies. */ public <T> List<T> getAllResolved(CharID id, QualifiedActor<SpecialAbility, T> qa) { return actOnQualifiedSet(id, qa); } }
public void loadCampaignFacets() { FacetLibrary.getFacet(DataSetInitializationFacet.class).initialize(this); }
protected void setUpContext() throws PersistenceLayerException { ChooserFactory.pushChooserClassname(RandomChooser.class.getName()); TokenRegistration.clearTokens(); TokenRegistration.register(AUTO_LANG_TOKEN); TokenRegistration.register(ABILITY_VISIBLE_TOKEN); TokenRegistration.register(AUTO_TOKEN); TokenRegistration.register(CHOOSE_TOKEN); TokenRegistration.register(CHOOSE_LANG_TOKEN); TokenRegistration.register(ABILITY_MULT_TOKEN); TokenRegistration.register(EQUIP_TYPE_TOKEN); TokenRegistration.register(EQUIP_PROFICIENCY_TOKEN); TokenRegistration.register(LANGBONUS_PRIM); TokenRegistration.register(PC_QUAL); TokenRegistration.register(getToken()); TokenRegistration.register(plugin.bonustokens.Feat.class); directAbilityFacet = FacetLibrary.getFacet(DirectAbilityFacet.class); activeEqModFacet = FacetLibrary.getFacet(ActiveEqModFacet.class); alignmentFacet = FacetLibrary.getFacet(AlignmentFacet.class); bioSetFacet = FacetLibrary.getFacet(BioSetFacet.class); checkFacet = FacetLibrary.getFacet(CheckFacet.class); classFacet = FacetLibrary.getFacet(ClassFacet.class); classLevelFacet = FacetLibrary.getFacet(ClassLevelFacet.class); companionModFacet = FacetLibrary.getFacet(CompanionModFacet.class); deityFacet = FacetLibrary.getFacet(DeityFacet.class); domainFacet = FacetLibrary.getFacet(DomainFacet.class); expandedCampaignFacet = FacetLibrary.getFacet(ExpandedCampaignFacet.class); languageFacet = FacetLibrary.getFacet(LanguageFacet.class); raceFacet = FacetLibrary.getFacet(RaceInputFacet.class); sizeFacet = FacetLibrary.getFacet(SizeFacet.class); skillFacet = FacetLibrary.getFacet(SkillFacet.class); statFacet = FacetLibrary.getFacet(StatFacet.class); templateInputFacet = FacetLibrary.getFacet(TemplateInputFacet.class); templateConsolidationFacet = FacetLibrary.getFacet(TemplateFacet.class); weaponProfModelFacet = FacetLibrary.getFacet(WeaponProfModelFacet.class); Globals.createEmptyRace(); Globals.setUseGUI(false); Globals.emptyLists(); GameMode gamemode = SettingsHandler.getGame(); gamemode.clearLoadContext(); str = BuildUtilities.createStat("Strength", "STR"); str.put(VariableKey.getConstant("LOADSCORE"), FormulaFactory.getFormulaFor("STRSCORE")); str.put(VariableKey.getConstant("OFFHANDLIGHTBONUS"), FormulaFactory.getFormulaFor(2)); dex = BuildUtilities.createStat("Dexterity", "DEX"); PCStat con = BuildUtilities.createStat("Constitution", "CON"); intel = BuildUtilities.createStat("Intelligence", "INT"); wis = BuildUtilities.createStat("Wisdom", "WIS"); cha = BuildUtilities.createStat("Charisma", "CHA"); AbstractReferenceContext ref = Globals.getContext().getReferenceContext(); lg = BuildUtilities.createAlignment("Lawful Good", "LG"); ref.importObject(lg); ln = BuildUtilities.createAlignment("Lawful Neutral", "LN"); ref.importObject(ln); le = BuildUtilities.createAlignment("Lawful Evil", "LE"); ref.importObject(le); ng = BuildUtilities.createAlignment("Neutral Good", "NG"); ref.importObject(ng); tn = BuildUtilities.createAlignment("True Neutral", "TN"); ref.importObject(tn); ne = BuildUtilities.createAlignment("Neutral Evil", "NE"); ref.importObject(ne); cg = BuildUtilities.createAlignment("Chaotic Good", "CG"); ref.importObject(cg); cn = BuildUtilities.createAlignment("Chaotic Neutral", "CN"); ref.importObject(cn); ce = BuildUtilities.createAlignment("Chaotic Evil", "CE"); ref.importObject(ce); ref.importObject(BuildUtilities.createAlignment("None", "NONE")); ref.importObject(BuildUtilities.createAlignment("Deity's", "Deity")); gamemode.setBonusFeatLevels("3|3"); SettingsHandler.setGame("3.5"); ref.importObject(str); ref.importObject(dex); ref.importObject(con); ref.importObject(intel); ref.importObject(wis); ref.importObject(cha); fine = BuildUtilities.createSize("Fine"); diminutive = BuildUtilities.createSize("Diminutive"); tiny = BuildUtilities.createSize("Tiny"); small = BuildUtilities.createSize("Small"); medium = BuildUtilities.createSize("Medium"); medium.put(ObjectKey.IS_DEFAULT_SIZE, Boolean.TRUE); large = BuildUtilities.createSize("Large"); huge = BuildUtilities.createSize("Huge"); gargantuan = BuildUtilities.createSize("Gargantuan"); colossal = BuildUtilities.createSize("Colossal"); context = Globals.getContext(); create(Language.class, "Common"); BuildUtilities.createFact(context, "ClassType", PCClass.class); FactDefinition<?, String> fd = BuildUtilities.createFact(context, "SpellType", PCClass.class); fd.setSelectable(true); context.getReferenceContext().importObject(AbilityCategory.FEAT); SourceFileLoader.createLangBonusObject(Globals.getContext()); }
/** * An AbstractQualifiedListFacet is a DataFacet that contains information about QualifyingObjects * that are contained in a PlayerCharacter when a PlayerCharacter may have more than one of that * type of QualifyingObject (e.g. Language, PCTemplate), the source of that object should be * tracked, and the PlayerCharacter can qualify for the object (they have prerequisites) * * <p>This class is designed to assume that each QualifyingObject may only be contained one time by * the PlayerCharacter, even if received from multiple sources. The QualifyingObject will only * trigger one DATA_ADDED event (when added by the first source) and if removed by some sources, * will only trigger one DATA_REMOVED event (when it is removed by the last remaining source). * Sources do not need to be removed in the order in which they are added, and the first source to * be added does not possess special status with respect to triggering a DATA_REMOVED event (it will * only trigger removal if it was the last source when removed) * * <p>The sources stored in this AbstractQualifiedListFacet are stored as a List, meaning the list * of sources may contain the same source multiple times. If so, each call to remove will only * remove that source one time from the list of sources. * * <p>In general, QualifyingObjects that are stored in an AbstractQualifiedListFacet are those where * the Prerequisites are those that are considered requirements. This means that as the Player * Character changes, the state of the Prerequisite can change and alter whether the underlying * object is granted to the Player Character. For PCGen 5.16, this will mean things like the * Prerequisite on the end of an ABILITY token (which are continuously evaluated) not the PRExxx: * tokens that appear directly on the line of an Ability in the Ability LST file (those are * evaluated only once, when the Ability is first added to the Player Character) * * <p>null is a valid source but a valid item to be added to the list of objects stored by * AbstractQualifiedListFacet. * * @author Thomas Parker (thpr [at] yahoo.com) */ public abstract class AbstractQualifiedListFacet<T extends QualifyingObject> extends AbstractDataFacet<CharID, T> { private PrerequisiteFacet prereqFacet = FacetLibrary.getFacet(PrerequisiteFacet.class); /** * Add the given object with the given source to the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID * * @param id The CharID representing the Player Character for which the given item should be added * @param obj The object to be added to the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID * @param source The source for the given object */ public void add(CharID id, T obj, Object source) { if (obj == null) { throw new IllegalArgumentException("Object to add may not be null"); } Map<T, Set<Object>> map = getConstructingCachedMap(id); Set<Object> set = map.get(obj); boolean fireNew = (set == null); if (fireNew) { set = new WrappedMapSet<Object>(IdentityHashMap.class); map.put(obj, set); } set.add(source); if (fireNew) { fireDataFacetChangeEvent(id, obj, DataFacetChangeEvent.DATA_ADDED); } } /** * Adds all of the objects in the given Collection to the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID. All * objects are added as if granted with the given source. * * @param id The CharID representing the Player Character for which the given items should be * added * @param c The Collection of objects to be added to the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID * @param source The source for the objects in the given Collection * @throws NullPointerException if the given Collection is null */ public void addAll(CharID id, Collection<? extends T> c, Object source) { for (T obj : c) { add(id, obj, source); } } /** * Removes the given source entry from the list of sources for the given object stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID. If the * given source was the only source for the given object, then the object is removed from the list * of objects stored in this AbstractQualifiedListFacet for the Player Character represented by * the given CharID and a removal event is fired. * * @param id The CharID representing the Player Character from which the given item source should * be removed * @param obj The object for which the source should be removed * @param source The source for the given object to be removed from the list of sources. */ public void remove(CharID id, T obj, Object source) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { processRemoval(id, componentMap, obj, source); } } /** * Removes the given source entry from the list of sources for all of the objects in the given * Collection for the Player Character represented by the given CharID. If the given source was * the only source for any of the objects in the collection, then those objects are removed from * the list of objects stored in this AbstractQualifiedListFacet for the Player Character * represented by the given CharID and a removal event is fired. * * @param id The CharID representing the Player Character from which the given items should be * removed * @param c The Collection of objects to be removed from the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID * @param source The source for the objects in the given Collection to be removed from the list of * sources. * @throws NullPointerException if the given Collection is null */ public void removeAll(CharID id, Collection<T> c, Object source) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { for (T obj : c) { processRemoval(id, componentMap, obj, source); } } } /** * Removes all objects (and all sources for those objects) from the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID * * <p>This method is value-semantic in that ownership of the returned Map is transferred to the * class calling this method. Since this is a remove all function, modification of the returned * Map will not modify this AbstractQualifiedListFacet and modification of this * AbstractQualifiedListFacet will not modify the returned Map. Modifications to the returned Map * will also not modify any future or previous objects returned by this (or other) methods on * AbstractQualifiedListFacet. If you wish to modify the information stored in this * AbstractQualifiedListFacet, you must use the add*() and remove*() methods of * AbstractQualifiedListFacet. * * @param id The CharID representing the Player Character from which all items should be removed * @return A non-null Set of objects removed from the list of objects stored in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID */ public Map<T, Set<Object>> removeAll(CharID id) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap == null) { return Collections.emptyMap(); } removeCache(id); for (T obj : componentMap.keySet()) { fireDataFacetChangeEvent(id, obj, DataFacetChangeEvent.DATA_REMOVED); } return componentMap; } /** * Returns a non-null copy of the Set of objects in this AbstractQualifiedListFacet for the Player * Character represented by the given CharID. This method returns an empty set if no objects are * in this AbstractQualifiedListFacet for the Player Character identified by the given CharID. * * <p>This method is value-semantic in that ownership of the returned List is transferred to the * class calling this method. Modification of the returned List will not modify this * AbstractQualifiedListFacet and modification of this AbstractQualifiedListFacet will not modify * the returned List. Modifications to the returned List will also not modify any future or * previous objects returned by this (or other) methods on AbstractQualifiedListFacet. If you wish * to modify the information stored in this AbstractQualifiedListFacet, you must use the add*() * and remove*() methods of AbstractQualifiedListFacet. * * @param id The CharID representing the Player Character for which the items in this * AbstractQualifiedListFacet should be returned. * @return A non-null Set of objects in this AbstractQualifiedListFacet for the Player Character * represented by the given CharID */ public Set<T> getSet(CharID id) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap == null) { return Collections.emptySet(); } return Collections.unmodifiableSet(componentMap.keySet()); } /** * Returns the count of items in this AbstractQualifiedListFacet for the Player Character * represented by the given CharID * * @param id The CharID representing the Player Character for which the count of items should be * returned * @return The count of items in this AbstractQualifiedListFacet for the Player Character * represented by the given CharID */ public int getCount(CharID id) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap == null) { return 0; } return componentMap.size(); } /** * Returns true if this AbstractQualifiedListFacet does not contain any items for the Player * Character represented by the given CharID * * @param id The CharId representing the PlayerCharacter to test if any items are contained by * this AbstractsSourcedListFacet * @return true if this AbstractQualifiedListFacet does not contain any items for the Player * Character represented by the given CharID; false otherwise (if it does contain items for * the Player Character) */ public boolean isEmpty(CharID id) { Map<T, Set<Object>> componentMap = getCachedMap(id); return componentMap == null || componentMap.isEmpty(); } /** * Returns true if this AbstractQualifiedListFacet contains the given value in the list of items * for the Player Character represented by the given CharID. * * @param id The CharID representing the Player Character used for testing * @param obj The object to test if this AbstractQualifiedListFacet contains that item for the * Player Character represented by the given CharID * @return true if this AbstractQualifiedListFacet contains the given value for the Player * Character represented by the given CharID; false otherwise */ public boolean contains(CharID id, T obj) { /* * TODO obj == null? - log an error? * * This should share behavior with AbstractListFacet */ Map<T, Set<Object>> componentMap = getCachedMap(id); return componentMap != null && componentMap.containsKey(obj); } /** * Returns a Set of sources for this AbstractQualifiedListFacet, the PlayerCharacter represented * by the given CharID, and the given object. Will add the given object to the list of items for * the PlayerCharacter represented by the given CharID and will return a new, empty Set if no * information has been set in this AbstractQualifiedListFacet for the given CharID and given * object. Will not return null. * * <p>Note that this method SHOULD NOT be public. The Set object is owned by * AbstractQualifiedListFacet, and since it can be modified, a reference to that object should not * be exposed to any object other than AbstractQualifiedListFacet. * * @param id The CharID for which the Set should be returned * @param obj The object for which the Set of sources should be returned * @return The Set of sources for the given object and Player Character represented by the given * CharID. */ private Set<Object> getConstructingCachedSetFor(CharID id, T obj) { Map<T, Set<Object>> map = getConstructingCachedMap(id); Set<Object> set = map.get(obj); if (set == null) { set = new WrappedMapSet<Object>(IdentityHashMap.class); map.put(obj, set); } return set; } /** * Returns the type-safe Map for this AbstractQualifiedListFacet and the given CharID. May return * null if no information has been set in this AbstractQualifiedListFacet for the given CharID. * * <p>Note that this method SHOULD NOT be public. The Map is owned by AbstractQualifiedListFacet, * and since it can be modified, a reference to that object should not be exposed to any object * other than AbstractQualifiedListFacet. * * @param id The CharID for which the Set should be returned * @return The Set for the Player Character represented by the given CharID; null if no * information has been set in this AbstractQualifiedListFacet for the Player Character. */ private Map<T, Set<Object>> getCachedMap(CharID id) { return (Map<T, Set<Object>>) getCache(id); } /** * Returns a type-safe Map for this AbstractQualifiedListFacet and the given CharID. Will return a * new, empty Map if no information has been set in this AbstractQualifiedListFacet for the given * CharID. Will not return null. * * <p>Note that this method SHOULD NOT be public. The Map object is owned by * AbstractQualifiedListFacet, and since it can be modified, a reference to that object should not * be exposed to any object other than AbstractQualifiedListFacet. * * @param id The CharID for which the Map should be returned * @return The Map for the Player Character represented by the given CharID. */ private Map<T, Set<Object>> getConstructingCachedMap(CharID id) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap == null) { componentMap = getComponentMap(); setCache(id, componentMap); } return componentMap; } /** * Returns a new (empty) Map for this AbstractQualifiedListFacet. Can be overridden by classes * that extend AbstractQualifiedListFacet if a Map other than an IdentityHashMap is desired for * storing the information in the AbstractQualifiedListFacet. * * <p>Note that this method SHOULD NOT be public. The Map object is owned by * AbstractQualifiedListFacet, and since it can be modified, a reference to that object should not * be exposed to any object other than AbstractQualifiedListFacet. * * <p>Note that this method should always be the only method used to construct a Map for this * AbstractQualifiedListFacet. It is actually preferred to use getConstructingCacheMap(CharID) in * order to implicitly call this method. * * @return A new (empty) Map for use in this AbstractQualifiedListFacet. */ protected Map<T, Set<Object>> getComponentMap() { return new IdentityHashMap<T, Set<Object>>(); } /** * Copies the contents of the AbstractQualifiedListFacet from one Player Character to another * Player Character, based on the given CharIDs representing those Player Characters. * * <p>This is a method in AbstractQualifiedListFacet in order to avoid exposing the mutable Map * object to other classes. This should not be inlined, as the Map is internal information to * AbstractQualifiedListFacet and should not be exposed to other classes. * * <p>Note also the copy is a one-time event and no references are maintained between the Player * Characters represented by the given CharIDs (meaning once this copy takes place, any change to * the AbstractQualifiedListFacet of one Player Character will only impact the Player Character * where the AbstractQualifiedListFacet was changed). * * @param source The CharID representing the Player Character from which the information should be * copied * @param destination The CharID representing the Player Character to which the information should * be copied */ @Override public void copyContents(CharID source, CharID destination) { Map<T, Set<Object>> sourceMap = getCachedMap(source); if (sourceMap != null) { for (Map.Entry<T, Set<Object>> me : sourceMap.entrySet()) { T obj = me.getKey(); Set<Object> sourceSet = me.getValue(); Set<Object> targetSet = getConstructingCachedSetFor(destination, obj); targetSet.addAll(sourceSet); } } } /** * This method implements removal of a source for an object contained by this * AbstractQualifiedListFacet. This implements the actual check that determines if the given * source was the only source for the given object. If so, then that object is removed from the * list of objects stored in this AbstractQualifiedListFacet for the Player Character represented * by the given CharID. * * @param id The CharID representing the Player Character which may have the given item removed. * @param componentMap The (private) Map for this AbstractQualifiedListFacet that will as least * have the given source removed from the list for the given object. * @param obj The object which may be removed if the given source is the only source for this * object in the Player Character represented by the given CharID * @param source The source for the given object to be removed from the list of sources for that * object */ private void processRemoval(CharID id, Map<T, Set<Object>> componentMap, T obj, Object source) { if (obj == null) { throw new IllegalArgumentException("Object to remove may not be null"); } Set<Object> set = componentMap.get(obj); if (set != null) { set.remove(source); if (set.isEmpty()) { componentMap.remove(obj); fireDataFacetChangeEvent(id, obj, DataFacetChangeEvent.DATA_REMOVED); } } } /** * Removes all information for the given source from this AbstractQualifiedListFacet for the * PlayerCharacter represented by the given CharID. * * @param id The CharID representing the Player Character for which items from the given source * will be removed * @param source The source for the objects to be removed from the list of items stored for the * Player Character identified by the given CharID */ public void removeAll(CharID id, Object source) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { /* * This list exists primarily to eliminate the possibility of a * concurrent modification exception on a recursive remove */ List<T> removedKeys = new ArrayList<T>(); for (Iterator<Map.Entry<T, Set<Object>>> it = componentMap.entrySet().iterator(); it.hasNext(); ) { Entry<T, Set<Object>> me = it.next(); Set<Object> set = me.getValue(); if (set.remove(source) && set.isEmpty()) { T obj = me.getKey(); it.remove(); removedKeys.add(obj); } } if (componentMap.isEmpty()) { removeCache(id); } for (T obj : removedKeys) { fireDataFacetChangeEvent(id, obj, DataFacetChangeEvent.DATA_REMOVED); } } } /** * Returns a non-null copy of the Set of objects in this AbstractQualifiedListFacet for the Player * Character represented by the given CharID and the given source. This method returns an empty * set if no objects are in this AbstractQualifiedListFacet for the Player Character identified by * the given CharID and source. * * <p>This method is value-semantic in that ownership of the returned List is transferred to the * class calling this method. Modification of the returned List will not modify this * AbstractQualifiedListFacet and modification of this AbstractQualifiedListFacet will not modify * the returned List. Modifications to the returned List will also not modify any future or * previous objects returned by this (or other) methods on AbstractQualifiedListFacet. If you wish * to modify the information stored in this AbstractQualifiedListFacet, you must use the add*() * and remove*() methods of AbstractQualifiedListFacet. * * @param id The CharID representing the Player Character for which the items in this * AbstractQualifiedListFacet should be returned. * @param owner The source object for which a copy of the List of objects in this * AbstractQualifiedListFacet should be returned. * @return A non-null Set of objects in this AbstractQualifiedListFacet for the Player Character * represented by the given CharID */ public List<? extends T> getSet(CharID id, Object owner) { List<T> list = new ArrayList<T>(); Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { for (Iterator<Map.Entry<T, Set<Object>>> it = componentMap.entrySet().iterator(); it.hasNext(); ) { Entry<T, Set<Object>> me = it.next(); Set<Object> set = me.getValue(); if (set.contains(owner)) { list.add(me.getKey()); } } } return list; } /** * Returns a non-null copy of the Set of objects the character qualifies for in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID. This * method returns an empty set if the Player Character identified by the given CharID qualifies * for none of the objects in this AbstractQualifiedListFacet. * * <p>This method is value-semantic in that ownership of the returned Collection is transferred to * the class calling this method. Modification of the returned Collection will not modify this * AbstractQualifiedListFacet and modification of this AbstractQualifiedListFacet will not modify * the returned Collection. Modifications to the returned Collection will also not modify any * future or previous objects returned by this (or other) methods on AbstractQualifiedListFacet. * If you wish to modify the information stored in this AbstractQualifiedListFacet, you must use * the add*() and remove*() methods of AbstractQualifiedListFacet. * * @param id The CharID representing the Player Character for which the items in this * AbstractQualifiedListFacet should be returned. * @return A non-null Set of objects the Player Character represented by the given CharID * qualifies for in this AbstractQualifiedListFacet */ public Collection<T> getQualifiedSet(CharID id) { Set<T> set = new HashSet<T>(); Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { for (Map.Entry<T, Set<Object>> me : componentMap.entrySet()) { T obj = me.getKey(); Set<Object> sources = me.getValue(); for (Object source : sources) { if (prereqFacet.qualifies(id, obj, source)) { set.add(obj); break; } } } } return set; } /** * Returns a non-null copy of the Set of objects the character qualifies for in this * AbstractQualifiedListFacet for the Player Character represented by the given CharID and the * given source. This method returns an empty set if the Player Character identified by the given * CharID qualifies for none of the objects in this AbstractQualifiedListFacet granted by the * given source. * * <p>This method is value-semantic in that ownership of the returned List is transferred to the * class calling this method. Modification of the returned List will not modify this * AbstractQualifiedListFacet and modification of this AbstractQualifiedListFacet will not modify * the returned List. Modifications to the returned List will also not modify any future or * previous objects returned by this (or other) methods on AbstractQualifiedListFacet. If you wish * to modify the information stored in this AbstractQualifiedListFacet, you must use the add*() * and remove*() methods of AbstractQualifiedListFacet. * * <p>Generally, use of this method is discouraged in general operational aspects. However, it is * recognized that certain output tokens can list certain items by source, and thus this method is * required, and it is unreasonable to expect complete elimination of this method or entirely * prohibit future use of this method. * * @param id The CharID representing the Player Character for which the items in this * AbstractQualifiedListFacet should be returned. * @param source The source object for which a copy of the List of objects the Player Character * qualifies for should be returned. * @return A non-null Set of objects the Player Character represented by the given CharID * qualifies for in this AbstractQualifiedListFacet */ public Collection<T> getQualifiedSet(CharID id, Object source) { Set<T> set = new WrappedMapSet<T>(IdentityHashMap.class); Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { for (Map.Entry<T, Set<Object>> me : componentMap.entrySet()) { T obj = me.getKey(); Set<Object> sources = me.getValue(); if (sources.contains(source)) { if (prereqFacet.qualifies(id, obj, source)) { set.add(obj); } } } } return set; } /** * Acts on the Set of objects the character qualifies for in this AbstractQualifiedListFacet for * the Player Character represented by the given CharID. The results of each action as provided by * the given QualifiedActor are returned in a non-null List. * * <p>This method returns an empty List if the Player Character identified by the given CharID * qualifies for none of the objects in this AbstractQualifiedListFacet. * * <p>This method is value-semantic in that ownership of the returned List is transferred to the * class calling this method. Modification of the returned List will not modify this * AbstractQualifiedListFacet and modification of this AbstractQualifiedListFacet will not modify * the returned List. Modifications to the returned List will also not modify any future or * previous objects returned by this (or other) methods on AbstractQualifiedListFacet. If you wish * to modify the information stored in this AbstractQualifiedListFacet, you must use the add*() * and remove*() methods of AbstractQualifiedListFacet. * * <p>Note: If a particular item has been granted by more than one source, then the QualifiedActor * will only be called for the first source that (successfully grants) the underlying object. * * @param id The CharID representing the Player Character for which the items in this * AbstractQualifiedListFacet should be returned. * @param qa The QualifiedActor which will act on each of the items in this * AbstractQualifiedListFacet for which the Player Character qualifies. * @return A non-null List of objects created by the QualifiedActor from each of the objects in * this AbstractQualifiedListFacet for which the Player Character qualifies. */ public <R> List<R> actOnQualifiedSet(CharID id, QualifiedActor<T, R> qa) { List<R> list = new ArrayList<R>(); Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { for (Map.Entry<T, Set<Object>> me : componentMap.entrySet()) { T obj = me.getKey(); Set<Object> sources = me.getValue(); for (Object source : sources) { if (prereqFacet.qualifies(id, obj, source)) { list.add(qa.act(obj, source)); } } } } return list; } public Collection<Object> getSources(CharID id, T obj) { Map<T, Set<Object>> componentMap = getCachedMap(id); if (componentMap != null) { Set<Object> sources = componentMap.get(obj); if (sources != null) { return Collections.unmodifiableSet(sources); } } return Collections.emptySet(); } }