/*
* PreReqListParser.java
* Copyright 2001 (C) Bryan McRoberts <merton_monk@yahoo.com>
* Copyright 2003 (C) Chris Ward <frugal@purplewombat.co.uk>
*
* 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 November 28, 2003
*
* Current Ver: $Revision$
*
*/
package pcgen.persistence.lst.prereq;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import pcgen.core.prereq.Prerequisite;
import pcgen.core.prereq.PrerequisiteOperator;
import pcgen.persistence.PersistenceLayerException;
import pcgen.rules.persistence.token.ParseResult;
import pcgen.util.Logging;
/**
* Abstract PRE parser, provides common parsing for many PRE tokens.
*/
public abstract class AbstractPrerequisiteListParser
extends AbstractPrerequisiteParser
implements PrerequisiteParserInterface
{
protected void convertKeysToSubKeys(Prerequisite prereq, String kind)
{
if (prereq == null)
{
return;
}
if (prereq.getKind() != null && prereq.getKind().equalsIgnoreCase(kind))
{
String key = prereq.getKey().trim();
int index = key.indexOf('(');
int endIndex = key.lastIndexOf(')');
if ((index >= 0) && (endIndex == key.length() - 1))
{
String subKey = key.substring(index + 1, endIndex).trim();
key = key.substring(0, index).trim();
prereq.setKey(key);
prereq.setSubKey(subKey);
}
}
for (Prerequisite element : prereq.getPrerequisites())
{
convertKeysToSubKeys(element, kind);
}
}
/**
* Parse the pre req list.
*
* @param kind The kind of the prerequisite (less the "PRE" prefix)
* @param formula The body of the prerequisite.
* @param invertResult Whether the prerequisite should invert the result.
* @param overrideQualify
* if set true, this prerequisite will be enforced in spite
* of any "QUALIFY" tag that may be present.
* @return PreReq
* @throws PersistenceLayerException
*/
@Override
public Prerequisite parse(
String kind,
String formula,
boolean invertResult,
boolean overrideQualify)
throws PersistenceLayerException
{
Prerequisite prereq = super.parse(kind, formula, invertResult, overrideQualify);
parsePrereqListType(prereq, kind, formula);
if (invertResult)
{
prereq.setOperator(prereq.getOperator().invert());
}
return prereq;
}
/*
* Parses a PRE type, some examples below:
*
* CLASS:1,Spellcaster=3
* <prereq kind="class" key="Spellcaster" min="3" />
*
* SKILL:1,Heal=5
* <prereq kind="skill" key="Heal" min="5" />
*
* FEAT:1,TYPE=Necromantic
* <prereq kind="feat" key="TYPE=Necromantic" />
*
* SKILL:2,Knowledge (Anthropology),Knowledge (Biology),Knowledge
* (Chemistry)=5
* <prereq min="2">
* <prereq kind="skill" key="Knowledge (Anthropology)" min="5" />
* <prereq kind="skill" key="Knowledge (Biology)" min="5" />
* <prereq kind="skill" key="Knowledge (Chemistry)" min="5" />
* </prereq>
*
* FEAT:2,CHECKMULT,Spell Focus
* <prereq kind="feat" count-multiples="true" key="feat.spell_focus" />
*
* FEAT:2,CHECKMULT,Spell Focus,[Spell Focus(Enchantment)]
* <prereq min="2">
* <prereq kind="feat" key="feat.spell_focus" count-multiples="true" min="2"/>
* <prereq kind="feat" key="feat.spell_focus_enchantment" logical="not" />
* </prereq>
*
* STAT:1,DEX=9,STR=13
* <prereq operator="gteq" op1="1">
* <prereq kind="stat" key="dex" operator="gteq" op1="9" />
* <prereq kind="stat" key="str" operator="gteq" op1="13" />
* </prereq>
*/
protected void parsePrereqListType(
Prerequisite prereq,
String kind,
String formula) throws PersistenceLayerException
{
// Sanity checking
ParseResult parseResult = checkForIllegalSeparator(kind, ',', formula);
if (!parseResult.passed())
{
throw new PersistenceLayerException(parseResult.toString());
}
if (!allowsNegate() && (formula.indexOf("[") >= 0 || formula.indexOf("]") >= 0))
{
throw new PersistenceLayerException("Prerequisite " + kind
+ " can not contain []: " + formula);
}
if (formula.indexOf("|") >= 0)
{
throw new PersistenceLayerException("Prerequisite " + kind
+ " can not contain |: " + formula);
}
String[] elements = formula.split(",");
int numRequired;
try
{
numRequired = Integer.parseInt(elements[0]);
if (elements.length == 1)
{
throw new PersistenceLayerException("Prerequisite " + kind
+ " can not have only a count: " + formula);
}
}
catch (NumberFormatException nfe)
{
throw new PersistenceLayerException("'" + elements[0]
+ "' is not a valid integer");
}
// Examine the last element to see if it is of the form "foo=n"
int elementsLength = elements.length;
for (int i = elementsLength - 1; i >= 0; --i)
{
if ("CHECKMULT".equalsIgnoreCase(elements[i]))
{
prereq.setCountMultiples(true);
--elementsLength;
}
}
// Token now contains all of the possible matches,
// min contains the target number (if there is one)
// number contains the number of 'tokens' that be be at least 'min'
if (elementsLength > 2)
{
// we have more than one option, so use a group
prereq.setOperator(PrerequisiteOperator.GTEQ);
prereq.setOperand(Integer.toString(numRequired));
prereq.setKind(null);
boolean hasKeyValue = false;
boolean hasKeyOnly = false;
int min = -99;
for (int i = 1; i < elements.length; i++)
{
String thisElement = elements[i];
if ("CHECKMULT".equals(thisElement))
{
continue;
}
boolean warnIgnored = isNoWarnElement(thisElement);
Prerequisite subreq = new Prerequisite();
subreq.setKind(kind.toLowerCase());
subreq.setCountMultiples(true);
if (thisElement.indexOf('=') >= 0)
{
// The element is either of the form "TYPE=foo" or "DEX=9"
// if it is the later, we need to extract the '9'
subreq.setOperator(PrerequisiteOperator.GTEQ);
String[] tokens = thisElement.split("=");
try
{
int valueIndx = tokens.length-1;
min = Integer.parseInt(tokens[valueIndx]);
subreq.setOperand(Integer.toString(min));
String requirementKey = getRequirementKey(tokens);
subreq.setKey(requirementKey);
// now back fill all of the previous prereqs with this minimum
for (Prerequisite p : new ArrayList<>(prereq.getPrerequisites()))
{
if (p.getOperand().equals("-99"))
{
p.setOperand(Integer.toString(min));
// If this requirement has already been added, we don't want to repeat it.
if (p.getKey().equals(requirementKey))
{
prereq.removePrerequisite(p);
}
}
}
if (!warnIgnored)
{
hasKeyValue = true;
}
}
catch (NumberFormatException nfe)
{
subreq.setKey(thisElement);
if (!warnIgnored)
{
hasKeyOnly = true;
}
}
}
else
{
if (requiresValue())
{
throw new PersistenceLayerException(
"Prerequisites of kind "
+ kind
+ " require a target value, e.g. Key=Value");
}
String assumed = getAssumedValue();
if (assumed == null)
{
subreq.setOperand(Integer.toString(min));
}
else
{
Logging.deprecationPrint("Old syntax detected: "
+ "Prerequisites of kind " + kind
+ " now require a target value, "
+ "e.g. Key=Value. Assuming Value=" + assumed);
subreq.setOperand(assumed);
}
if (!warnIgnored)
{
hasKeyOnly = true;
}
subreq.setKey(thisElement);
subreq.setOperator(PrerequisiteOperator.GTEQ);
}
subreq.setOperand(Integer.toString(min));
prereq.addPrerequisite(subreq);
}
for (Prerequisite element : prereq.getPrerequisites())
{
if (element.getOperand().equals("-99"))
{
element.setOperand("1");
}
}
if (hasKeyOnly && hasKeyValue)
{
Logging
.deprecationPrint("You are using a deprecated syntax of PRE"
+ kind
+ ":"
+ formula
+ " ... Each item in the list should have a target value, e.g.: PRE"
+ kind + ":1,First=99,Second=5");
}
}
else
{
// We only have a number of prereqs to pass, and a single prereq so we do not want a
// wrapper prereq around a list of 1 element.
// i.e. 1,Alertness, or 2,TYPE=ItemCreation, or 1,Reflex=7 or 3,Knowledge%=2 or 4,TYPE.Craft=5
Prerequisite subreq = prereq;
if (elementsLength > 1)
{
for (int i = 1; i < elements.length; ++i)
{
if ("CHECKMULT".equalsIgnoreCase(elements[i]))
{
continue;
}
if (elements[i].indexOf('=') >= 0)
{
// i.e. TYPE=ItemCreation or Reflex=7
String[] tokens = elements[i].split("=");
int valueIdx = tokens.length-1;
try
{
// i.e. Reflex=7 or TYPE.Craft=5
int iOper = Integer.parseInt(tokens[valueIdx]);
if (numRequired != 1)
{
//
// If we would lose the required number of matches,
// then make this a PREMULT
//
prereq.setOperator(PrerequisiteOperator.GTEQ);
prereq
.setOperand(Integer.toString(numRequired));
prereq.setKind(null);
subreq = new Prerequisite();
prereq.addPrerequisite(subreq);
subreq.setCountMultiples(true);
}
subreq.setOperand(Integer.toString(iOper));
String requirementKey = getRequirementKey(tokens);
subreq.setKey(requirementKey);
}
catch (NumberFormatException nfe)
{
if (tokens[valueIdx].equals("ANY"))
{
if (isAnyLegal())
{
subreq.setOperand(tokens[valueIdx]);
subreq.setKey(getRequirementKey(tokens));
}
else
{
throw new PersistenceLayerException(
"Prerequisites of kind " + kind
+ " do not support 'ANY'");
}
}
else
{
// i.e. TYPE=ItemCreation
subreq.setOperand(elements[0]);
subreq.setKey(elements[i]);
}
}
}
else
{
if (requiresValue())
{
throw new PersistenceLayerException(
"Prerequisites of kind "
+ kind
+ " require a target value, e.g. Key=Value");
}
String assumed = getAssumedValue();
if (assumed == null)
{
subreq.setOperand(elements[0]);
}
else
{
Logging.deprecationPrint("Old syntax detected: "
+ "Prerequisites of kind " + kind
+ " now require a target value, "
+ "e.g. Key=Value. Assuming Value=" + assumed);
subreq.setOperand(assumed);
}
subreq.setKey(elements[i]);
}
break;
}
}
else
{
subreq.setOperand(elements[0]);
}
subreq.setKind(kind.toLowerCase());
subreq.setOperator(PrerequisiteOperator.GTEQ);
}
}
private String getRequirementKey(String[] tokens)
{
String reqKey;
if (tokens.length == 2)
{
reqKey = tokens[0];
}
else
{
List<String> parts = new ArrayList<>();
parts.addAll(Arrays.asList(tokens));
parts.remove(parts.size()-1);
reqKey = StringUtils.join(parts, "=");
}
return reqKey;
}
protected boolean isNoWarnElement(String thisElement)
{
return false;
}
protected boolean isAnyLegal()
{
return true;
}
protected String getAssumedValue()
{
return null;
}
protected boolean requiresValue()
{
return false;
}
/**
* @return Does this PREreq kind allow [] for negation
*/
protected boolean allowsNegate()
{
return false;
}
/**
* Flag each Prerequisite created to indicate that no character is
* required to successfully test the Prerequisite. The function is
* recursive to handle a single Prerequisite that gets split out
* into a premult.
*
* @param prereq the new no need for char
*/
protected void setNoNeedForChar(Prerequisite prereq)
{
if (prereq == null)
{
return;
}
prereq.setCharacterRequired(false);
for (Prerequisite element : prereq.getPrerequisites())
{
setNoNeedForChar(element);
}
}
}