Search in sources :

Example 6 with DomainSpellList

use of pcgen.cdom.list.DomainSpellList in project pcgen by PCGen.

the class DomainsToken method unparse.

@Override
public String[] unparse(LoadContext context, Spell spell) {
    DoubleKeyMapToList<Prerequisite, Integer, CDOMReference<DomainSpellList>> dkmtl = new DoubleKeyMapToList<>();
    List<String> list = new ArrayList<>();
    Changes<CDOMReference<DomainSpellList>> masterChanges = context.getListContext().getMasterListChanges(getTokenName(), spell, SPELLLIST_CLASS);
    if (masterChanges.includesGlobalClear()) {
        list.add(Constants.LST_DOT_CLEAR_ALL);
    }
    if (masterChanges.hasRemovedItems()) {
        for (CDOMReference<DomainSpellList> swl : masterChanges.getRemoved()) {
            AssociatedChanges<Spell> changes = context.getListContext().getChangesInMasterList(getTokenName(), spell, swl);
            MapToList<Spell, AssociatedPrereqObject> map = changes.getRemovedAssociations();
            if (map != null && !map.isEmpty()) {
                for (Spell added : map.getKeySet()) {
                    if (!spell.getLSTformat().equals(added.getLSTformat())) {
                        context.addWriteMessage("Spell " + getTokenName() + " token cannot remove another Spell " + "(must only remove itself)");
                        return null;
                    }
                    for (AssociatedPrereqObject assoc : map.getListFor(added)) {
                        List<Prerequisite> prereqs = assoc.getPrerequisiteList();
                        if (prereqs != null && !prereqs.isEmpty()) {
                            context.addWriteMessage("Incoming Remove " + "Edge to " + spell.getKeyName() + " had a " + "Prerequisite: " + prereqs.size());
                            return null;
                        }
                        dkmtl.addToListFor(null, -1, swl);
                    }
                }
            }
        }
    }
    for (CDOMReference<DomainSpellList> swl : masterChanges.getAdded()) {
        AssociatedChanges<Spell> changes = context.getListContext().getChangesInMasterList(getTokenName(), spell, swl);
        Collection<Spell> removedItems = changes.getRemoved();
        if (removedItems != null && !removedItems.isEmpty() || changes.includesGlobalClear()) {
            context.addWriteMessage(getTokenName() + " does not support .CLEAR.");
            return null;
        }
        MapToList<Spell, AssociatedPrereqObject> map = changes.getAddedAssociations();
        if (map != null && !map.isEmpty()) {
            for (Spell added : map.getKeySet()) {
                if (!spell.getLSTformat().equals(added.getLSTformat())) {
                    context.addWriteMessage("Spell " + getTokenName() + " token cannot allow another Spell " + "(must only allow itself)");
                    return null;
                }
                for (AssociatedPrereqObject assoc : map.getListFor(added)) {
                    List<Prerequisite> prereqs = assoc.getPrerequisiteList();
                    Prerequisite prereq;
                    if (prereqs == null || prereqs.isEmpty()) {
                        prereq = null;
                    } else if (prereqs.size() == 1) {
                        prereq = prereqs.get(0);
                    } else {
                        context.addWriteMessage("Incoming Edge to " + spell.getKeyName() + " had more than one " + "Prerequisite: " + prereqs.size());
                        return null;
                    }
                    Integer level = assoc.getAssociation(AssociationKey.SPELL_LEVEL);
                    if (level == null) {
                        context.addWriteMessage("Incoming Allows Edge to " + spell.getKeyName() + " had no Spell Level defined");
                        return null;
                    }
                    if (level.intValue() < 0) {
                        context.addWriteMessage("Incoming Allows Edge to " + spell.getKeyName() + " had invalid Level: " + level + ". Must be >= 0.");
                        return null;
                    }
                    dkmtl.addToListFor(prereq, level, swl);
                }
            }
        }
    }
    if (dkmtl.isEmpty()) {
        if (list.isEmpty()) {
            // Legal if no DOMAINS was present in the Spell
            return null;
        } else {
            return list.toArray(new String[list.size()]);
        }
    }
    PrerequisiteWriter prereqWriter = new PrerequisiteWriter();
    SortedSet<CDOMReference<DomainSpellList>> set = new TreeSet<>(ReferenceUtilities.REFERENCE_SORTER);
    SortedSet<Integer> levelSet = new TreeSet<>();
    for (Prerequisite prereq : dkmtl.getKeySet()) {
        StringBuilder sb = new StringBuilder();
        boolean needPipe = false;
        levelSet.clear();
        levelSet.addAll(dkmtl.getSecondaryKeySet(prereq));
        for (Integer i : levelSet) {
            set.clear();
            set.addAll(dkmtl.getListFor(prereq, i));
            if (needPipe) {
                sb.append(Constants.PIPE);
            }
            sb.append(ReferenceUtilities.joinLstFormat(set, Constants.COMMA));
            sb.append('=').append(i);
            needPipe = true;
        }
        if (prereq != null) {
            sb.append('[');
            StringWriter swriter = new StringWriter();
            try {
                prereqWriter.write(swriter, prereq);
            } catch (PersistenceLayerException e) {
                context.addWriteMessage("Error writing Prerequisite: " + e);
                return null;
            }
            sb.append(swriter.toString());
            sb.append(']');
        }
        list.add(sb.toString());
    }
    return list.toArray(new String[list.size()]);
}
Also used : PrerequisiteWriter(pcgen.persistence.lst.output.prereq.PrerequisiteWriter) ArrayList(java.util.ArrayList) Spell(pcgen.core.spell.Spell) StringWriter(java.io.StringWriter) TreeSet(java.util.TreeSet) AssociatedPrereqObject(pcgen.cdom.base.AssociatedPrereqObject) DoubleKeyMapToList(pcgen.base.util.DoubleKeyMapToList) DomainSpellList(pcgen.cdom.list.DomainSpellList) PersistenceLayerException(pcgen.persistence.PersistenceLayerException) CDOMReference(pcgen.cdom.base.CDOMReference) Prerequisite(pcgen.core.prereq.Prerequisite)

Example 7 with DomainSpellList

use of pcgen.cdom.list.DomainSpellList in project pcgen by PCGen.

the class PlayerCharacter method getTotalCasterLevelWithSpellBonus.

public int getTotalCasterLevelWithSpellBonus(CharacterSpell acs, final Spell aSpell, final String spellType, final String classOrRace, final int casterLev) {
    if (aSpell != null && acs.getFixedCasterLevel() != null) {
        return getVariableValue(acs.getFixedCasterLevel(), Constants.EMPTY_STRING).intValue();
    }
    int tBonus = casterLev;
    boolean replaceCasterLevel = false;
    String tType;
    String tStr;
    // final List<TypedBonus> bonuses = new ArrayList<TypedBonus>();
    final List<CasterLevelSpellBonus> bonuses = new ArrayList<>();
    if (classOrRace != null) {
        // bonuses.addAll(getBonusesTo("CASTERLEVEL", classOrRace));
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", classOrRace);
        if (tBonus > 0) {
            tType = getSpellBonusType("CASTERLEVEL", classOrRace);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
        // BONUS:CASTERLEVEL|CLASS.Sorcerer|1
        if (!classOrRace.startsWith("RACE.")) {
            tStr = "CLASS." + classOrRace;
            // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
            tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
            if (tBonus > 0) {
                tType = getSpellBonusType("CASTERLEVEL", tStr);
                bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
            }
        }
    }
    if (aSpell == null) {
        return tallyCasterlevelBonuses(casterLev, replaceCasterLevel, bonuses);
    }
    if (!spellType.equals(Constants.NONE)) {
        tStr = "TYPE." + spellType;
        // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
        tStr += ".RESET";
        // final List<TypedBonus> reset = getBonusesTo("CASTERLEVEL", tStr);
        // if ( reset.size() > 0 )
        // {
        // bonuses.addAll(reset);
        // replaceCasterLevel = true;
        // }
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            replaceCasterLevel = true;
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
    }
    tStr = "SPELL." + aSpell.getKeyName();
    // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
    tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
    if (tBonus > 0) {
        tType = getSpellBonusType("CASTERLEVEL", tStr);
        bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
    }
    tStr += ".RESET";
    // final List<TypedBonus> reset = getBonusesTo("CASTERLEVEL", tStr);
    // if ( reset.size() > 0 )
    // {
    // bonuses.addAll(reset);
    // replaceCasterLevel = true;
    // }
    tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
    if (tBonus > 0) {
        replaceCasterLevel = true;
        tType = getSpellBonusType("CASTERLEVEL", tStr);
        bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
    }
    /*
		 * This wraps in TreeSet because it looks to me like this is ordered
		 * (given .RESET)
		 */
    for (SpellSchool school : new TreeSet<>(aSpell.getSafeListFor(ListKey.SPELL_SCHOOL))) {
        tStr = "SCHOOL." + school.toString();
        // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (// Allow negative bonus to casterlevel
        tBonus != 0) {
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
        tStr += ".RESET";
        // final List<TypedBonus> reset1 = getBonusesTo("CASTERLEVEL",
        // tStr);
        // if ( reset.size() > 0 )
        // {
        // bonuses.addAll(reset1);
        // replaceCasterLevel = true;
        // }
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            replaceCasterLevel = true;
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
    }
    for (String subschool : new TreeSet<>(aSpell.getSafeListFor(ListKey.SPELL_SUBSCHOOL))) {
        tStr = "SUBSCHOOL." + subschool;
        // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
        tStr += ".RESET";
        // final List<TypedBonus> reset1 = getBonusesTo("CASTERLEVEL",
        // tStr);
        // if ( reset.size() > 0 )
        // {
        // bonuses.addAll(reset1);
        // replaceCasterLevel = true;
        // }
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            replaceCasterLevel = true;
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
    }
    //Not wrapped because it wasn't in 5.14
    for (String desc : aSpell.getSafeListFor(ListKey.SPELL_DESCRIPTOR)) {
        tStr = "DESCRIPTOR." + desc;
        // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
        tStr += ".RESET";
        // final List<TypedBonus> reset1 = getBonusesTo("CASTERLEVEL",
        // tStr);
        // if ( reset.size() > 0 )
        // {
        // bonuses.addAll(reset1);
        // replaceCasterLevel = true;
        // }
        tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
        if (tBonus > 0) {
            replaceCasterLevel = true;
            tType = getSpellBonusType("CASTERLEVEL", tStr);
            bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
        }
    }
    final HashMapToList<CDOMList<Spell>, Integer> domainMap = getSpellLevelInfo(aSpell);
    if (domainMap != null) {
        for (CDOMList<Spell> spellList : domainMap.getKeySet()) {
            if (spellList instanceof DomainSpellList) {
                tStr = "DOMAIN." + spellList.getKeyName();
                // bonuses.addAll( getBonusesTo("CASTERLEVEL", tStr) );
                tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
                if (tBonus > 0) {
                    tType = getSpellBonusType("CASTERLEVEL", tStr);
                    bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
                }
                tStr += ".RESET";
                // final List<TypedBonus> reset1 =
                // getBonusesTo("CASTERLEVEL", tStr);
                // if ( reset.size() > 0 )
                // {
                // bonuses.addAll(reset1);
                // replaceCasterLevel = true;
                // }
                tBonus = (int) getTotalBonusTo("CASTERLEVEL", tStr);
                if (tBonus > 0) {
                    replaceCasterLevel = true;
                    tType = getSpellBonusType("CASTERLEVEL", tStr);
                    bonuses.add(new CasterLevelSpellBonus(tBonus, tType));
                }
            }
        }
    }
    int result = tallyCasterlevelBonuses(casterLev, replaceCasterLevel, bonuses);
    return (result);
}
Also used : ArrayList(java.util.ArrayList) Spell(pcgen.core.spell.Spell) CharacterSpell(pcgen.core.character.CharacterSpell) DomainSpellList(pcgen.cdom.list.DomainSpellList) SpellSchool(pcgen.cdom.identifier.SpellSchool) TreeSet(java.util.TreeSet) CDOMList(pcgen.cdom.base.CDOMList)

Example 8 with DomainSpellList

use of pcgen.cdom.list.DomainSpellList in project pcgen by PCGen.

the class DomainListToken method allow.

@Override
public boolean allow(PlayerCharacter pc, Spell spell) {
    DomainSpellList list = spelllist.get();
    DataSetID datasetID = pc.getCharID().getDatasetID();
    for (AvailableSpell availSpell : masterAvailableSpellFacet.getMatchingSpellsInList(list, datasetID, spell)) {
        int level = availSpell.getLevel();
        if (level >= 0 && allow(pc, level, "", spell, list)) {
            return true;
        }
    }
    return false;
}
Also used : DomainSpellList(pcgen.cdom.list.DomainSpellList) AvailableSpell(pcgen.cdom.helper.AvailableSpell) DataSetID(pcgen.cdom.enumeration.DataSetID)

Example 9 with DomainSpellList

use of pcgen.cdom.list.DomainSpellList in project pcgen by PCGen.

the class DomainsToken method parseTokenWithSeparator.

@Override
protected ParseResult parseTokenWithSeparator(LoadContext context, Spell spell, String value) {
    // Note: May contain PRExxx
    String domainKey;
    Prerequisite prereq = null;
    int openBracketLoc = value.indexOf('[');
    if (openBracketLoc == -1) {
        domainKey = value;
    } else {
        if (value.lastIndexOf(']') != value.length() - 1) {
            return new ParseResult.Fail("Invalid " + getTokenName() + " must end with ']' if it contains a PREREQ tag", context);
        }
        domainKey = value.substring(0, openBracketLoc);
        String prereqString = value.substring(openBracketLoc + 1, value.length() - 1);
        if (prereqString.isEmpty()) {
            return new ParseResult.Fail(getTokenName() + " cannot have empty prerequisite : " + value, context);
        }
        prereq = getPrerequisite(prereqString);
        if (prereq == null) {
            return new ParseResult.Fail(getTokenName() + " had invalid prerequisite : " + prereqString, context);
        }
    }
    boolean foundAny = false;
    boolean foundOther = false;
    StringTokenizer pipeTok = new StringTokenizer(domainKey, Constants.PIPE);
    while (pipeTok.hasMoreTokens()) {
        // could be name=x or name,name=x
        String tokString = pipeTok.nextToken();
        int equalLoc = tokString.indexOf(Constants.EQUALS);
        if (equalLoc == -1) {
            return new ParseResult.Fail("Malformed " + getTokenName() + " Token (expecting an =): " + tokString, context);
        }
        if (equalLoc != tokString.lastIndexOf(Constants.EQUALS)) {
            return new ParseResult.Fail("Malformed " + getTokenName() + " Token (more than one =): " + tokString, context);
        }
        String nameList = tokString.substring(0, equalLoc);
        String levelString = tokString.substring(equalLoc + 1);
        Integer level;
        try {
            level = Integer.valueOf(levelString);
            if (level.intValue() < -1) {
                return new ParseResult.Fail(getTokenName() + " may not use a negative level: " + value, context);
            } else if (level.intValue() == -1) {
                if (prereq != null) {
                    return new ParseResult.Fail(getTokenName() + " may not use -1 with a PREREQ: " + value, context);
                }
            // Logging.deprecationPrint(getTokenName()
            // + " should not use a negative level: " + value);
            }
        } catch (NumberFormatException nfe) {
            return new ParseResult.Fail("Malformed Level in " + getTokenName() + " (expected an Integer): " + levelString, context);
        }
        ParseResult pr = checkForIllegalSeparator(',', nameList);
        if (!pr.passed()) {
            return pr;
        }
        StringTokenizer commaTok = new StringTokenizer(nameList, Constants.COMMA);
        while (commaTok.hasMoreTokens()) {
            CDOMReference<DomainSpellList> ref;
            String token = commaTok.nextToken();
            if (Constants.LST_ALL.equals(token)) {
                foundAny = true;
                ref = context.getReferenceContext().getCDOMAllReference(SPELLLIST_CLASS);
            } else {
                foundOther = true;
                ref = TokenUtilities.getTypeOrPrimitive(context, SPELLLIST_CLASS, token);
            }
            if (ref == null) {
                return new ParseResult.Fail("  Error was in " + getTokenName(), context);
            }
            if (level == -1) {
                //No need to check for prereq here - done above
                context.getListContext().removeFromMasterList(getTokenName(), spell, ref, spell);
            } else {
                AssociatedPrereqObject edge = context.getListContext().addToMasterList(getTokenName(), spell, ref, spell);
                edge.setAssociation(AssociationKey.SPELL_LEVEL, level);
                if (prereq != null) {
                    edge.addPrerequisite(prereq);
                }
                context.getObjectContext().addToList(spell, ListKey.SPELL_DOMAINLEVEL, token + ' ' + level);
            }
        }
    }
    if (foundAny && foundOther) {
        return new ParseResult.Fail("Non-sensical " + getTokenName() + ": Contains ANY and a specific reference: " + value, context);
    }
    return ParseResult.SUCCESS;
}
Also used : ParseResult(pcgen.rules.persistence.token.ParseResult) DomainSpellList(pcgen.cdom.list.DomainSpellList) StringTokenizer(java.util.StringTokenizer) Prerequisite(pcgen.core.prereq.Prerequisite) AssociatedPrereqObject(pcgen.cdom.base.AssociatedPrereqObject)

Example 10 with DomainSpellList

use of pcgen.cdom.list.DomainSpellList in project pcgen by PCGen.

the class AvailableSpellFacet method getSpellLevelInfo.

/**
	 * Returns a non-null HashMapToList indicating the spell levels and sources
	 * of those spell levels available to a Player Character for a given Spell.
	 * 
	 * This may return multiple spell levels because it is possible for a spell
	 * to be accessible to a Player Character at multiple levels since it may be
	 * available from multiple sources. This also returns the spell lists
	 * associated with the given level, since it is possible for a multi-class
	 * character to have access to the same spell at different levels. By
	 * returning the source as well as the spell levels, such scenarios can be
	 * appropriately distinguished.
	 * 
	 * This method is value-semantic in that ownership of the returned
	 * HashMapToList is transferred to the class calling this method.
	 * Modification of the returned HashMapToList will not modify this
	 * AvailableSpellFacet and modification of this AvailableSpellFacet will not
	 * modify the returned HashMapToList. Modifications to the returned
	 * HashMapToList will also not modify any future or previous objects
	 * returned by this (or other) methods on AvailableSpellFacet. If you wish
	 * to modify the information stored in this AvailableSpellFacet, you must
	 * use the add*() and remove*() methods of AvailableSpellFacet.
	 * 
	 * @param id
	 *            The CharID identifying the Player Character for which the
	 *            spell levels should be returned
	 * @param sp
	 *            The Spell for which the spell levels should be returned
	 * @return A non-null HashMapToList indicating the spell levels and sources
	 *         of those spell levels available to a Player Character for a given
	 *         Spell.
	 */
public HashMapToList<CDOMList<Spell>, Integer> getSpellLevelInfo(CharID id, Spell sp) {
    HashMapToList<CDOMList<Spell>, Integer> levelInfo = new HashMapToList<>();
    Map<CDOMList<Spell>, Map<Integer, Map<Spell, Set<Object>>>> listMap = (Map<CDOMList<Spell>, Map<Integer, Map<Spell, Set<Object>>>>) getCache(id);
    if (listMap == null) {
        return levelInfo;
    }
    for (Entry<CDOMList<Spell>, Map<Integer, Map<Spell, Set<Object>>>> me : listMap.entrySet()) {
        CDOMList<Spell> list = me.getKey();
        //Check to ensure we don't use SPELLS:
        if (!(list instanceof ClassSpellList) && !(list instanceof DomainSpellList)) {
            continue;
        }
        Map<Integer, Map<Spell, Set<Object>>> levelMap = me.getValue();
        for (Map.Entry<Integer, Map<Spell, Set<Object>>> lme : levelMap.entrySet()) {
            Integer level = lme.getKey();
            Map<Spell, Set<Object>> spellMap = lme.getValue();
            if (spellMap.containsKey(sp)) {
                levelInfo.addToListFor(list, level);
            } else {
                for (Spell spell : spellMap.keySet()) {
                    if (spell.getKeyName().equals(sp.getKeyName())) {
                        if (Logging.isLoggable(Logging.INFO)) {
                            Logging.log(Logging.INFO, "Found alternate spell of same key: " + spell + " from " + spell.getSource() + " rather than " + sp.getSource());
                        }
                        levelInfo.addToListFor(list, level);
                    }
                }
            }
        }
    }
    return levelInfo;
}
Also used : Set(java.util.Set) ClassSpellList(pcgen.cdom.list.ClassSpellList) Spell(pcgen.core.spell.Spell) DomainSpellList(pcgen.cdom.list.DomainSpellList) HashMapToList(pcgen.base.util.HashMapToList) CDOMList(pcgen.cdom.base.CDOMList) Map(java.util.Map)

Aggregations

DomainSpellList (pcgen.cdom.list.DomainSpellList)13 Spell (pcgen.core.spell.Spell)7 ClassSpellList (pcgen.cdom.list.ClassSpellList)6 PCClass (pcgen.core.PCClass)6 AssociatedPrereqObject (pcgen.cdom.base.AssociatedPrereqObject)5 CDOMList (pcgen.cdom.base.CDOMList)4 ArrayList (java.util.ArrayList)3 CDOMReference (pcgen.cdom.base.CDOMReference)3 AvailableSpell (pcgen.cdom.helper.AvailableSpell)3 Domain (pcgen.core.Domain)3 CharacterSpell (pcgen.core.character.CharacterSpell)3 StringTokenizer (java.util.StringTokenizer)2 TreeSet (java.util.TreeSet)2 MasterListInterface (pcgen.cdom.base.MasterListInterface)2 DataSetID (pcgen.cdom.enumeration.DataSetID)2 ClassSource (pcgen.cdom.helper.ClassSource)2 Prerequisite (pcgen.core.prereq.Prerequisite)2 StringWriter (java.io.StringWriter)1 HashSet (java.util.HashSet)1 Map (java.util.Map)1