/* * AbilityInfoPanel.java * Copyright 2006 (C) Aaron Divinsky <boomer70@yahoo.com> * * 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 * * Current Ver: $Revision$ */ package pcgen.core; import java.util.ArrayList; import java.util.Collections; import java.util.List; import pcgen.base.lang.StringUtil; import pcgen.cdom.base.ChooseDriver; import pcgen.cdom.base.ConcretePrereqObject; import pcgen.cdom.base.Constants; import pcgen.cdom.content.CNAbility; import pcgen.io.EntityEncoder; import pcgen.persistence.lst.output.prereq.PrerequisiteWriter; import pcgen.util.Logging; /** * This class represents a generic description field. * * <p>The class supports the description object having one or more prerequisites * as well as performing variable substitution on the string itself. * * <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 boomer70 <boomer70@yahoo.com> * */ public class Description extends ConcretePrereqObject { private 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_CHOICE = "%CHOICE"; //$NON-NLS-1$ private static final String VAR_LIST = "%LIST"; //$NON-NLS-1$ private static final String VAR_FEATS = "%FEAT="; //$NON-NLS-1$ private static final String VAR_MARKER = "$$VAR:"; //$NON-NLS-1$ /** * Default constructor. * * @param aString The description string. */ public Description( 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", replacement); //$NON-NLS-1$ } 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)); if (Logging.isLoggable(Logging.LST_WARNING)) { Logging .log( Logging.LST_WARNING, "The % without a number in the description '" + aString + "' should be either escaped e.g. %% or made into a parameter reference e.g. %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 description string after having tested all prereqs and * substituting all variables. * * @param aPC The PlayerCharacter used to evaluate formulas. * * @return The fully substituted description string. */ public String getDescription( final PlayerCharacter aPC, List<? extends Object> objList ) { if (objList.isEmpty()) { return Constants.EMPTY_STRING; } PObject sampleObject; Object b = objList.get(0); if (b instanceof PObject) { sampleObject = (PObject) b; } else if (b instanceof CNAbility) { sampleObject = ((CNAbility) b).getAbility(); } else { Logging .errorPrint("Unable to resolve Description with object of type: " + b.getClass().getName()); return Constants.EMPTY_STRING; } final StringBuilder buf = new StringBuilder(250); if (this.qualifies(aPC, sampleObject)) { 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() ) { continue; } final String var = theVariables.get(ind - 1); if ( var.equals(VAR_NAME) ) { if ( sampleObject != null ) { buf.append(sampleObject.getOutputName()); } } else if ( var.equals(VAR_CHOICE) ) { if (b instanceof ChooseDriver) { ChooseDriver object = (ChooseDriver) b; if (aPC.hasAssociations(object)) { //TODO This is ill defined buf.append(aPC.getAssociationList(object).get(0)); } } else { Logging .errorPrint("In Description resolution, " + "Ignoring object of type: " + b.getClass().getName() + " because " + VAR_CHOICE + " was requested but the object does not support CHOOSE"); continue; } } else if ( var.equals(VAR_LIST) ) { List<String> assocList = new ArrayList<>(); for (Object obj : objList) { if (obj instanceof ChooseDriver) { ChooseDriver object = (ChooseDriver) obj; if (aPC.hasAssociations(object)) { assocList.addAll(aPC.getAssociationList(object)); } } else { Logging .errorPrint("In Description resolution, " + "Ignoring object of type: " + b.getClass().getName() + " because " + VAR_CHOICE + " was requested but the object does not support CHOOSE"); continue; } } String joinString; if (assocList.size() == 2) { joinString = " and "; } else { joinString = ", "; } Collections.sort(assocList); buf.append(StringUtil.joinToStringBuilder(assocList, joinString)); } else if ( var.startsWith(VAR_FEATS) ) { final String featName = var.substring(VAR_FEATS.length()); List<CNAbility> feats; if (featName.startsWith("TYPE=") || featName.startsWith("TYPE.")) { feats = aPC.getCNAbilities(AbilityCategory.FEAT); } else { Ability feat = Globals.getContext().getReferenceContext() .silentlyGetConstructedCDOMObject( Ability.class, AbilityCategory.FEAT, featName); if (feat == null) { Logging .errorPrint("Found invalid Feat reference in Description: " + featName); } feats = aPC.getMatchingCNAbilities(feat); } boolean needSpace = false; for ( final CNAbility cna : feats ) { if (cna.getAbility().isType(featName.substring(5))) { if (needSpace) { buf.append(' '); } buf.append(aPC.getDescription(Collections.singletonList(cna))); needSpace = true; } } } else if ( var.startsWith("\"") ) //$NON-NLS-1$ { buf.append(var.substring(1, var.length() - 1)); } else { buf.append(aPC.getVariableValue(var, "Description").intValue()); //$NON-NLS-1$ } } else { buf.append(comp); } } } return buf.toString(); } /** * Gets the Description tag in PCC format. * * @return A String in LST file format for this description. * */ public String getPCCText() { final StringBuilder buf = new StringBuilder(250); for ( final String str : theComponents ) { if ( str.startsWith(VAR_MARKER) ) { final int ind = Integer.parseInt(str.substring(VAR_MARKER.length())); buf.append('%' + String.valueOf(ind)); } else if (str.equals("%")) { //reescape buf.append("%%"); } else { buf.append(EntityEncoder.encodeLight(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 * getPrerequisiteCount() + 31 * (theVariables == null ? 0 : theVariables.size()); } @Override public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof Description)) { return false; } Description other = (Description) o; if (theVariables == null) { if (other.theVariables != null) { return false; } } return theComponents.equals(other.theComponents) && (theVariables == null || theVariables.equals(other.theVariables)) && equalsPrereqObject(other); } }