/* * Copyright 2008 (C) Thomas 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 plugin.lsttokens; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import pcgen.base.formula.Formula; import pcgen.base.lang.StringUtil; import pcgen.base.text.ParsingSeparator; import pcgen.base.util.DoubleKeyMap; import pcgen.base.util.MapToList; import pcgen.base.util.TripleKeyMap; import pcgen.cdom.base.AssociatedPrereqObject; import pcgen.cdom.base.CDOMObject; import pcgen.cdom.base.CDOMReference; import pcgen.cdom.base.Constants; import pcgen.cdom.base.FormulaFactory; import pcgen.cdom.base.Ungranted; import pcgen.cdom.enumeration.AssociationKey; import pcgen.core.prereq.Prerequisite; import pcgen.core.spell.Spell; import pcgen.rules.context.AssociatedChanges; import pcgen.rules.context.LoadContext; import pcgen.rules.persistence.token.AbstractNonEmptyToken; import pcgen.rules.persistence.token.CDOMPrimaryToken; import pcgen.rules.persistence.token.ParseResult; /** * @author djones4 * */ public class SpellsLst extends AbstractNonEmptyToken<CDOMObject> implements CDOMPrimaryToken<CDOMObject> { @Override public String getTokenName() { return "SPELLS"; } /** * {@literal * SPELLS:<spellbook name>|[<optional parameters, pipe deliminated>] |<spell * name>[,<formula for DC>] |<spell name2>[,<formula2 for DC>] |PRExxx * |PRExxx * * CASTERLEVEL=<formula> Casterlevel of spells TIMES=<formula> Cast Times * per day, -1=At Will *} * @param sourceLine * Line from the LST file without the SPELLS: * @return spells list */ @Override protected ParseResult parseNonEmptyToken(LoadContext context, CDOMObject obj, String sourceLine) { if (obj instanceof Ungranted) { return new ParseResult.Fail("Cannot use " + getTokenName() + " on an Ungranted object type: " + obj.getClass().getSimpleName(), context); } if ((sourceLine == null) || sourceLine.isEmpty()) { return new ParseResult.Fail("Argument in " + getTokenName() + " cannot be empty", context); } if (sourceLine.equals(Constants.LST_DOT_CLEAR_ALL)) { context.getListContext().removeAllFromList(getTokenName(), obj, Spell.SPELLS); return ParseResult.SUCCESS; } ParsingSeparator sep = new ParsingSeparator(sourceLine, '|'); sep.addGroupingPair('[', ']'); sep.addGroupingPair('(', ')'); String spellBook = sep.next(); if (spellBook.isEmpty()) { return new ParseResult.Fail("SpellBook in " + getTokenName() + " cannot be empty", context); } // Formula casterLevel = null; String casterLevel = null; String times = null; String timeunit = null; if (!sep.hasNext()) { return new ParseResult.Fail(getTokenName() + ": minimally requires a Spell Name", context); } String token = sep.next(); while (true) { if (token.startsWith("TIMES=")) { if (times != null) { return new ParseResult.Fail( "Found two TIMES entries in " + getTokenName() + ": invalid: " + sourceLine, context); } times = token.substring(6); if (times.isEmpty()) { return new ParseResult.Fail( "Error in Times in " + getTokenName() + ": argument was empty", context); } if (!sep.hasNext()) { return new ParseResult.Fail(getTokenName() + ": minimally requires " + "a Spell Name (after TIMES=)", context); } token = sep.next(); } else if (token.startsWith("TIMEUNIT=")) { if (timeunit != null) { return new ParseResult.Fail( "Found two TIMEUNIT entries in " + getTokenName() + ": invalid: " + sourceLine, context); } timeunit = token.substring(9); if (timeunit.isEmpty()) { return new ParseResult.Fail( "Error in TimeUnit in " + getTokenName() + ": argument was empty", context); } if (!sep.hasNext()) { return new ParseResult.Fail(getTokenName() + ": minimally requires " + "a Spell Name (after TIMEUNIT=)", context); } token = sep.next(); } else if (token.startsWith("CASTERLEVEL=")) { if (casterLevel != null) { return new ParseResult.Fail( "Found two CASTERLEVEL entries in " + getTokenName() + ": invalid: " + sourceLine, context); } casterLevel = token.substring(12); if (casterLevel.isEmpty()) { return new ParseResult.Fail( "Error in Caster Level in " + getTokenName() + ": argument was empty", context); } if (!sep.hasNext()) { return new ParseResult.Fail(getTokenName() + ": minimally requires a " + "Spell Name (after CASTERLEVEL=)", context); } token = sep.next(); } else { break; } } if (times == null) { times = "1"; } if (token.isEmpty()) { return new ParseResult.Fail("Spell arguments may not be empty in " + getTokenName() + ": " + sourceLine, context); } if (token.charAt(0) == ',') { return new ParseResult.Fail(getTokenName() + " Spell arguments may not start with , : " + token, context); } if (token.charAt(token.length() - 1) == ',') { return new ParseResult.Fail(getTokenName() + " Spell arguments may not end with , : " + token, context); } if (token.indexOf(",,") != -1) { return new ParseResult.Fail(getTokenName() + " Spell arguments uses double separator ,, : " + token, context); } /* * CONSIDER This is currently order enforcing the reference fetching to * match the integration tests that we perform, and their current * behavior. Not sure if this is really tbe best solution? * * See CDOMObject. */ DoubleKeyMap<CDOMReference<Spell>, AssociationKey<?>, Object> dkm = new DoubleKeyMap<>( LinkedHashMap.class, HashMap.class); while (true) { if (token.isEmpty()) { return new ParseResult.Fail("Spell arguments may not end with comma or pipe in " + getTokenName() + ": " + sourceLine, context); } int commaLoc = token.indexOf(','); String name = commaLoc == -1 ? token : token.substring(0, commaLoc); CDOMReference<Spell> spell = context.getReferenceContext().getCDOMReference( Spell.class, name); dkm.put(spell, AssociationKey.CASTER_LEVEL, casterLevel); Formula timesFormula = FormulaFactory.getFormulaFor(times); if (!timesFormula.isValid()) { return new ParseResult.Fail("Times in " + getTokenName() + " was not valid: " + timesFormula.toString(), context); } dkm.put(spell, AssociationKey.TIMES_PER_UNIT, timesFormula); if (timeunit != null) { dkm.put(spell, AssociationKey.TIME_UNIT, timeunit); } dkm.put(spell, AssociationKey.SPELLBOOK, spellBook); if (commaLoc != -1) { dkm.put(spell, AssociationKey.DC_FORMULA, token .substring(commaLoc + 1)); } if (!sep.hasNext()) { // No prereqs, so we're done finish(context, obj, dkm, null); return ParseResult.SUCCESS; } token = sep.next(); if (looksLikeAPrerequisite(token)) { break; } } List<Prerequisite> prereqs = new ArrayList<>(); while (true) { Prerequisite prereq = getPrerequisite(token); if (prereq == null) { return new ParseResult.Fail( " (Did you put spells after the " + "PRExxx tags in SPELLS:?)", context); } prereqs.add(prereq); if (!sep.hasNext()) { break; } token = sep.next(); } finish(context, obj, dkm, prereqs); return ParseResult.SUCCESS; } public void finish(LoadContext context, CDOMObject obj, DoubleKeyMap<CDOMReference<Spell>, AssociationKey<?>, Object> dkm, List<Prerequisite> prereqs) { for (CDOMReference<Spell> spell : dkm.getKeySet()) { AssociatedPrereqObject edge = context.getListContext().addToList( getTokenName(), obj, Spell.SPELLS, spell); for (AssociationKey ak : dkm.getSecondaryKeySet(spell)) { edge.setAssociation(ak, dkm.get(spell, ak)); } if (prereqs != null) { for (Prerequisite prereq : prereqs) { edge.addPrerequisite(prereq); } } } } @Override public String[] unparse(LoadContext context, CDOMObject obj) { AssociatedChanges<CDOMReference<Spell>> changes = context .getListContext().getChangesInList(getTokenName(), obj, Spell.SPELLS); List<String> list = new ArrayList<>(); if (changes.includesGlobalClear()) { list.add(Constants.LST_DOT_CLEAR_ALL); } MapToList<CDOMReference<Spell>, AssociatedPrereqObject> mtl = changes .getAddedAssociations(); if (mtl != null && !mtl.isEmpty()) { list.addAll(processAdds(context, mtl)); } if (list.isEmpty()) { return null; } return list.toArray(new String[list.size()]); } private Collection<? extends String> processAdds(LoadContext context, MapToList<CDOMReference<Spell>, AssociatedPrereqObject> mtl) { TripleKeyMap<Set<Prerequisite>, Map<AssociationKey<?>, Object>, CDOMReference<Spell>, String> m = new TripleKeyMap<>(); for (CDOMReference<Spell> lw : mtl.getKeySet()) { for (AssociatedPrereqObject assoc : mtl.getListFor(lw)) { Map<AssociationKey<?>, Object> am = new HashMap<>(); String dc = null; for (AssociationKey<?> ak : assoc.getAssociationKeys()) { // if (AssociationKey.SOURCE_URI.equals(ak) // || AssociationKey.FILE_LOCATION.equals(ak)) // { // // Do nothing // } // else if (AssociationKey.DC_FORMULA.equals(ak)) { dc = assoc.getAssociation(AssociationKey.DC_FORMULA); } else { am.put(ak, assoc.getAssociation(ak)); } } m.put(new HashSet<>(assoc.getPrerequisiteList()), am, lw, dc); } } Set<String> set = new TreeSet<>(); for (Set<Prerequisite> prereqs : m.getKeySet()) { for (Map<AssociationKey<?>, Object> am : m .getSecondaryKeySet(prereqs)) { StringBuilder sb = new StringBuilder(); sb.append(am.get(AssociationKey.SPELLBOOK)); Formula times = AssociationKey.TIMES_PER_UNIT.cast(am .get(AssociationKey.TIMES_PER_UNIT)); sb.append(Constants.PIPE).append("TIMES=").append(times); String timeunit = AssociationKey.TIME_UNIT.cast(am .get(AssociationKey.TIME_UNIT)); if (timeunit != null) { sb.append(Constants.PIPE).append("TIMEUNIT=") .append(timeunit); } String casterLvl = AssociationKey.CASTER_LEVEL.cast(am .get(AssociationKey.CASTER_LEVEL)); if (casterLvl != null) { sb.append(Constants.PIPE).append("CASTERLEVEL=") .append(casterLvl); } Set<String> spellSet = new TreeSet<>(); for (CDOMReference<Spell> spell : m.getTertiaryKeySet(prereqs, am)) { String spellString = spell.getLSTformat(false); String dc = m.get(prereqs, am, spell); if (dc != null) { spellString += Constants.COMMA + dc; } spellSet.add(spellString); } sb.append(Constants.PIPE); sb.append(StringUtil.join(spellSet, Constants.PIPE)); if (prereqs != null && !prereqs.isEmpty()) { sb.append(Constants.PIPE); sb.append(getPrerequisiteString(context, prereqs)); } set.add(sb.toString()); } } return set; } @Override public Class<CDOMObject> getTokenClass() { return CDOMObject.class; } }