/* * Aspect.java * Copyright 2008 (C) James Dempsey * * 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 * * Created on 16/08/2008 16:36:13 * * $Id$ */ package pcgen.cdom.helper; import java.util.ArrayList; import java.util.Collections; import java.util.List; import pcgen.base.lang.StringUtil; import pcgen.cdom.base.ConcretePrereqObject; import pcgen.cdom.base.Constants; import pcgen.cdom.content.CNAbility; import pcgen.cdom.enumeration.AspectName; import pcgen.cdom.enumeration.MapKey; import pcgen.core.Ability; import pcgen.core.PlayerCharacter; import pcgen.io.EntityEncoder; import pcgen.persistence.lst.output.prereq.PrerequisiteWriter; import pcgen.util.Logging; /** * The Class {@code Aspect} represents a generic name field for * abilities. It is a name/value characteristic allowing substitution of * values. * * <p>Variable substitution is performed by replacing a placeholder indicated * by %# with the #th variable in the variable list. For example, the string * <br>{@code "This is %1 variable %3 %2"} * <br>would be replaced with the string "This is a variable substitution * string" if the variable list was "a","string", * "substitution". * * * @author James Dempsey <jdempsey@users.sourceforge.net> * */ public class Aspect extends ConcretePrereqObject { /** * The name of the name stored in this Aspect. */ private final AspectName key; private final List<String> theComponents = new ArrayList<>(); private List<String> theVariables = null; private static final String VAR_NAME = "%NAME"; //$NON-NLS-1$ private static final String VAR_LIST = "%LIST"; //$NON-NLS-1$ private static final String VAR_MARKER = "$$VAR:"; //$NON-NLS-1$ /** * Instantiates a new aspect. * * @param name the name of the aspect * @param aString the aspect string */ public Aspect(final String name, final String aString) { if (name == null) { throw new IllegalArgumentException( "Name for Aspect cannot be null"); } if (aString == null) { throw new IllegalArgumentException( "Value for Aspect cannot be null"); } this.key = AspectName.getConstant(name); parseAspectString(aString); } /** * Instantiates a new aspect. * * @param key the name of the aspect * @param aString the aspect string */ public Aspect(final AspectName key, final String aString) { if (key == null) { throw new IllegalArgumentException( "Key for Aspect cannot be null"); } if (aString == null) { throw new IllegalArgumentException( "Value for Aspect cannot be null"); } this.key = key; parseAspectString(aString); } /** * Parse an aspect definition string and populate the Aspect with * the contents. This drives the processing to split the description * from the parameters and to identify the references to the * parameters. * * @param aString The aspect definition string. */ private void parseAspectString(final String aString) { int currentInd = 0; int percentInd = -1; while ((percentInd = aString.indexOf('%', currentInd)) != -1) { final String preText = aString.substring(currentInd, percentInd); if (!preText.isEmpty()) { theComponents.add(preText); } if (percentInd == aString.length() - 1) { theComponents.add("%"); //$NON-NLS-1$ return; } if (aString.charAt(percentInd + 1) == '{') { // This is a bracketed placeholder. The replacement parameter // is contained within the {} currentInd = aString.indexOf('}', percentInd + 1) + 1; final String replacement = aString.substring(percentInd + 1, currentInd); // For the time being we will only support numerics here. try { Integer.parseInt(replacement); } catch (NumberFormatException nfe) { Logging.errorPrintLocalised( "Errors.Description.InvalidVariableReplacement", //$NON-NLS-1$ replacement); } theComponents.add(VAR_MARKER + replacement); } else if (aString.charAt(percentInd + 1) == '%') { // This is an escape sequence so we can actually print a % currentInd = percentInd + 2; theComponents.add("%"); //$NON-NLS-1$ } else { // In this case we have an unbracketed placeholder. We will // walk the string until such time as we no longer have a number currentInd = percentInd + 1; while (currentInd < aString.length()) { final char val = aString.charAt(currentInd); try { Integer.parseInt(String.valueOf(val)); currentInd++; } catch (NumberFormatException nfe) { break; } } if (currentInd > percentInd + 1) { theComponents.add(VAR_MARKER + aString.substring(percentInd+1, currentInd)); } else { // We broke out of the variable finding loop without finding // even a single integer. Assume we have a DESC field that // is using a % unescaped. theComponents.add(aString.substring(percentInd, percentInd+1)); } } } theComponents.add(aString.substring(currentInd)); } /** * Adds a variable to use in variable substitution. * * @param aVariable */ public void addVariable(final String aVariable) { if (theVariables == null) { theVariables = new ArrayList<>(); } theVariables.add(aVariable); } /** * Gets the name of the aspect. * * @return the aspect name */ public String getName() { return key.toString(); } /** * Gets the key of the aspect. * * @return the aspect key */ public AspectName getKey() { return key; } /** * Gets the name string after having substituting all variables. * * @param aPC The PlayerCharacter used to evaluate formulas. * @param abilities the abilities for which the Aspect text should be compiled * * @return The fully substituted description string. */ public String getAspectText(final PlayerCharacter aPC, List<CNAbility> abilities) { final StringBuilder buf = new StringBuilder(50); if ((abilities == null) || (abilities.isEmpty())) { return ""; } Ability sampleAbilityObject = abilities.get(0).getAbility(); if(!qualifies(aPC, sampleAbilityObject)) { return ""; } for (final String comp : theComponents) { if (comp.startsWith(VAR_MARKER)) { final int ind = Integer.parseInt(comp.substring(VAR_MARKER.length())); if (theVariables == null || ind > theVariables.size()) { buf.append(Constants.EMPTY_STRING); continue; } final String var = theVariables.get(ind - 1); if (var.equals(VAR_NAME)) { buf.append(sampleAbilityObject.getOutputName()); } else if (var.equals(VAR_LIST)) { List<String> assocList = new ArrayList<>(); for (CNAbility cna : abilities) { assocList.addAll(aPC.getAssociationList(cna)); } String joinString; if (assocList.size() == 2) { joinString = " and "; } else { joinString = ", "; } Collections.sort(assocList); buf.append(StringUtil.joinToStringBuilder(assocList, joinString)); } else if (var.startsWith("\"")) //$NON-NLS-1$ { buf.append(var.substring(1, var.length() - 1)); } else { buf.append(aPC.getVariableValue(var, "Aspect").intValue()); //$NON-NLS-1$ } } else { buf.append(comp); } } return buf.toString(); } /** * Gets the Aspect tag in PCC format. * * @return A String in LST file format for this description. * */ public String getPCCText() { final StringBuilder buf = new StringBuilder(); for (final String str : theComponents) { if (str.startsWith(VAR_MARKER)) { final int ind = Integer.parseInt(str.substring(VAR_MARKER.length())); buf.append('%').append(ind); } else if (str.equals("%")) { //reescape buf.append("%%"); } else { buf.append(EntityEncoder.encode(str)); } } if (theVariables != null) { for (final String var : theVariables) { buf.append(Constants.PIPE); buf.append(var); } } if (hasPrerequisites()) { buf.append(Constants.PIPE); buf.append(new PrerequisiteWriter().getPrerequisiteString( getPrerequisiteList(), Constants.PIPE)); } return buf.toString(); } @Override public String toString() { return getPCCText(); } @Override public int hashCode() { return theComponents.size() + 7 * (theVariables == null ? 0 : theVariables.size()); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof Aspect)) { return false; } Aspect other = (Aspect) obj; if (theVariables == null && other.theVariables != null) { return false; } return theComponents.equals(other.theComponents) && (theVariables == null || theVariables.equals(other.theVariables)); } public static String printAspect(PlayerCharacter pc, AspectName key, List<CNAbility> abilities, boolean printName) { if (abilities.isEmpty()) { return ""; } Ability sampleAbilityObject = abilities.get(0).getAbility(); StringBuilder buff = new StringBuilder(50); List<Aspect> aspects = sampleAbilityObject.get(MapKey.ASPECT, key); Aspect aspect = lastPassingAspect(aspects, pc, sampleAbilityObject); if (aspect != null) { if (printName) { buff.append(aspect.getName()).append(": "); } buff.append(aspect.getAspectText(pc, abilities)); } return buff.toString(); } public static String printAspect(PlayerCharacter pc, AspectName key, List<CNAbility> abilities) { return printAspect(pc, key, abilities, true); } public static String printAspectValue(PlayerCharacter pc, AspectName key, List<CNAbility> abilities) { return printAspect(pc, key, abilities, false); } public static Aspect lastPassingAspect(List<Aspect> aspects, PlayerCharacter pc, Ability a) { Aspect retAspect = null; if (aspects != null) { for (int i = 0; i < aspects.size(); i++) { Aspect testAspect = aspects.get(i); if (testAspect.qualifies(pc, a)) { retAspect = testAspect; } } } return retAspect; } }