/*
* CampaignSourceEntry.java
* Copyright 2003 (C) David Hibbs <sage_sam@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
*
* Created on November 17, 2003, 12:29 PM
*
* Current Ver: $Revision$
*
*/
package pcgen.persistence.lst;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.StringTokenizer;
import pcgen.base.lang.StringUtil;
import pcgen.base.util.HashMapToList;
import pcgen.base.util.MapToList;
import pcgen.cdom.base.Constants;
import pcgen.core.Campaign;
import pcgen.core.prereq.Prerequisite;
import pcgen.core.utils.CoreUtility;
import pcgen.persistence.PersistenceLayerException;
import pcgen.persistence.lst.output.prereq.PrerequisiteWriter;
import pcgen.persistence.lst.prereq.PreParserFactory;
import pcgen.util.Logging;
/**
* This class is used to match a source file to the campaign that
* loaded it.
*/
public class CampaignSourceEntry implements SourceEntry
{
private Campaign campaign = null;
private List<String> excludeItems = new ArrayList<>();
private List<String> includeItems = new ArrayList<>();
private List<Prerequisite> prerequisites = new ArrayList<>();
private URIEntry uri = null;
/**
* CampaignSourceEntry constructor.
*
* @param campaign Campaign that referenced the provided file.
* Must not be null.
* @param lstLoc URL path to an LST source file
* Must not be null.
*/
public CampaignSourceEntry(Campaign campaign, URI lstLoc)
{
super();
this.campaign = Objects.requireNonNull(campaign);
this.uri = new URIEntry(campaign.getDisplayName(), Objects.requireNonNull(lstLoc));
}
public CampaignSourceEntry(Campaign campaign, URIEntry entry)
{
super();
this.campaign = Objects.requireNonNull(campaign);
this.uri = Objects.requireNonNull(entry);
}
/**
* This method gets the Campaign that was the source of the
* file. (I.e. the reason it was loaded)
* @return Campaign that requested the file be loaded
*/
@Override
public Campaign getCampaign()
{
return campaign;
}
/**
* This method gets a list of the items contained in the given source
* file to exclude from getting saved in memory. All other objects
* in the file are to be included.
* @return List of String names of objects to exclude
*/
@Override
public List<String> getExcludeItems()
{
return excludeItems;
}
/**
* This method gets the file/path of the LST file.
* @return String url-formatted path to the LST file
*/
@Override
public URI getURI()
{
return uri.getURI();
}
/**
* This method gets a list of the items contained in the given source
* file to include in getting saved in memory. All other objects
* in the file are to be excluded.
* @return List of String names of objects to include
*/
@Override
public List<String> getIncludeItems()
{
return includeItems;
}
/**
* @param arg0
* @return true if equals
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object arg0)
{
if (arg0 == this)
{
return true;
}
if (!(arg0 instanceof CampaignSourceEntry))
{
return false;
}
CampaignSourceEntry other = (CampaignSourceEntry) arg0;
return uri.equals(other.uri) && excludeItems.equals(other.excludeItems)
&& includeItems.equals(other.includeItems);
}
/**
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return this.uri.getLSTformat().hashCode();
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
StringBuilder sBuff = new StringBuilder();
sBuff.append("Campaign: ");
sBuff.append(campaign.getDisplayName());
sBuff.append("; SourceFile: ");
sBuff.append(getURI());
return sBuff.toString();
}
public static CampaignSourceEntry getNewCSE(Campaign campaign2,
URI sourceUri, String value)
{
if (value == null || value.isEmpty())
{
Logging
.errorPrint("Cannot build CampaignSourceEntry for empty value in "
+ sourceUri);
return null;
}
// Check if include/exclude items were present
int pipePos = value.indexOf("|");
CampaignSourceEntry cse;
if (pipePos == -1)
{
if (value.startsWith("("))
{
Logging.errorPrint("Invalid Campaign File, cannot start with (:" + value);
return null;
}
URIEntry uri =
URIEntry.getURIEntry(campaign2.getDisplayName(), sourceUri,
value);
cse = new CampaignSourceEntry(campaign2, uri);
}
else
{
URIEntry uri =
URIEntry.getURIEntry(campaign2.getDisplayName(), sourceUri,
value.substring(0, pipePos));
cse = new CampaignSourceEntry(campaign2, uri);
// Get the include/exclude item string
String inExString = value.substring(pipePos + 1);
List<String> tagList = parseSuffix(inExString, sourceUri, value);
for (String tagString : tagList)
{
// Check for surrounding parens
if (tagString.startsWith("(("))
{
Logging
.errorPrint("Found Suffix in Campaign Source with multiple parenthesis: "
+ "Single set of parens required around INCLUDE/EXCLUDE");
Logging.errorPrint("Found: '" + tagString
+ "' in " + value);
return null;
}
// Update the include or exclude items list, as appropriate
if (tagString.startsWith("(INCLUDE:"))
{
// assume matching parens
tagString = inExString.substring(1, tagString.length() - 1);
List<String> splitIncExc = cse.splitInExString(tagString);
if (splitIncExc == null)
{
//Error
return null;
}
cse.includeItems = splitIncExc;
}
else if (tagString.startsWith("(EXCLUDE:"))
{
// assume matching parens
tagString = inExString.substring(1, tagString.length() - 1);
List<String> splitIncExc = cse.splitInExString(tagString);
if (splitIncExc == null)
{
//Error
return null;
}
cse.excludeItems = splitIncExc;
}
else if (PreParserFactory.isPreReqString(tagString))
{
Prerequisite prereq;
try
{
prereq = PreParserFactory.getInstance().parse(tagString);
}
catch (PersistenceLayerException e)
{
Logging.errorPrint(
"Error Initializing PreParserFactory.", e);
return null;
}
if (prereq == null)
{
Logging
.errorPrint("Found invalid prerequisite in Campaign Source: '"
+ tagString + "' in " + value);
return null;
}
cse.prerequisites.add(prereq);
}
else
{
Logging.errorPrint("Invalid Suffix (must have "
+ "'(INCLUDE' '(EXCLUDE' or a PRExxx immediately "
+ "following the pipe (no spaces). Found: '" + inExString
+ "' on Campaign Source: '" + value + "' in " + sourceUri);
return null;
}
}
validatePrereqs(cse.getPrerequisites(), sourceUri);
}
return cse;
}
/**
* Convert a string occurring after the first | into a list of tokens. We
* expect INCLUDE or EXCLUSE in brackets (as these can contain |)
* and PREreqs.
*
* @param suffix The string to be parsed, should only be the suffix
* @param sourceUri The source we can use to report errors against.
* @param value The full value we can use to report errors against.
* @return A list of the discrete tags that were specified, null if there
* was an error reported to the log.
*/
static List<String> parseSuffix(String suffix, URI sourceUri,
String value)
{
List<String> tagList = new ArrayList<>();
String currentTag = "";
int bracketLevel = 0;
StringTokenizer tokenizer =
new StringTokenizer(suffix, "|()", true);
while (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken();
if (token.equals("("))
{
currentTag += token;
bracketLevel++;
}
else if (token.equals(")"))
{
if (bracketLevel > 0)
{
bracketLevel--;
}
currentTag += token;
}
else if (token.equals("|"))
{
if (bracketLevel > 0)
{
currentTag += token;
}
else if (!currentTag.isEmpty())
{
tagList.add(currentTag);
currentTag = "";
}
}
else
{
currentTag += token;
}
}
if (!currentTag.isEmpty())
{
tagList.add(currentTag);
}
// Check for a bracket mismatch
if (bracketLevel > 0)
{
Logging
.errorPrint("Suffix in Campaign Source with missing closing parenthesis, Found: '"
+ suffix
+ "' on Campaign Source: '"
+ value
+ "' in "
+ sourceUri);
return null;
}
return tagList;
}
/**
* Check that all prerequisites specified in the PCC file are
* supported. Any unsupported prereqs will be reported as LST
* errors. This is a recursive function allowing it to
* check nested prereqs.
*
* @param prereqList The prerequisites to be checked.
*/
private static void validatePrereqs(List<Prerequisite> prereqList, URI sourceUri)
{
if (prereqList == null || prereqList.isEmpty())
{
return;
}
for (Prerequisite prereq : prereqList)
{
if (prereq.isCharacterRequired())
{
final PrerequisiteWriter prereqWriter =
new PrerequisiteWriter();
ArrayList<Prerequisite> displayList = new ArrayList<>();
displayList.add(prereq);
String lstString =
prereqWriter.getPrerequisiteString(displayList,
Constants.TAB);
Logging.log(Logging.LST_ERROR, "Prereq '" + prereq.getKind()
+ "' is not supported in PCC files. Prereq was '" + lstString
+ "' in " + sourceUri + ". Prereq will be ignored.");
}
else
{
validatePrereqs(prereq.getPrerequisites(), sourceUri);
}
}
}
/**
* Split an include or exclude string accounting for the possible presence
* of a leading category.
* @param inExString The string to be split
* @return A list of keys, optionally with leading category keys
*/
private List<String> splitInExString(String inExString)
{
boolean hasCategory = false;
boolean hasKeyOnly = false;
List<String> catKeyList = new ArrayList<>();
String target = inExString.substring(8);
if (target == null || target.length() == 0)
{
Logging.errorPrint("Must Specify Items after :");
return null;
}
List<String> keyList = CoreUtility.split(target, '|');
for (String key : keyList)
{
if (key.startsWith("CATEGORY="))
{
hasCategory = true;
List<String> abilityKeyList =
CoreUtility.split(key.substring(9), ',');
String category = abilityKeyList.get(0);
abilityKeyList.remove(0);
for (String string : abilityKeyList)
{
catKeyList.add(category + ',' + string);
}
}
else
{
hasKeyOnly = true;
catKeyList.add(key);
}
}
if (hasKeyOnly && hasCategory)
{
Logging.log(Logging.LST_ERROR, "Invalid "
+ inExString.substring(0, 7) + " value on " + uri.getLSTformat() + " in "
+ campaign.getDisplayName()
+ ". Abilities must always have categories (e.g. "
+ inExString.substring(0, 8)
+ "CATEGORY=cat1,key1,key2|CATEGORY=cat2,key1 ) and "
+ "other file types should never have categories (e.g. "
+ inExString.substring(0, 8) + "key1|key2 ).");
return null;
}
return catKeyList;
}
public String getLSTformat()
{
StringBuilder sb = new StringBuilder();
sb.append(uri.getLSTformat());
if (!includeItems.isEmpty())
{
sb.append(Constants.PIPE);
sb.append("(INCLUDE:");
sb.append(joinIncExcList(includeItems));
sb.append(')');
}
else if (!excludeItems.isEmpty())
{
sb.append(Constants.PIPE);
sb.append("(EXCLUDE:");
sb.append(joinIncExcList(excludeItems));
sb.append(')');
}
return sb.toString();
}
private StringBuilder joinIncExcList(List<String> list)
{
MapToList<String, String> map = new HashMapToList<>();
for (String s : list)
{
int commaLoc = s.indexOf(',');
if (commaLoc == -1)
{
return StringUtil.joinToStringBuilder(list, Constants.PIPE);
}
else
{
map.addToListFor(s.substring(0, commaLoc), s
.substring(commaLoc + 1));
}
}
StringBuilder sb = new StringBuilder(200);
boolean needPipe = false;
for (String category : map.getKeySet())
{
if (needPipe)
{
sb.append(Constants.PIPE);
}
needPipe = true;
sb.append("CATEGORY=");
sb.append(category);
sb.append(Constants.COMMA);
sb.append(StringUtil.joinToStringBuilder(map.getListFor(category),
Constants.COMMA));
}
return sb;
}
public CampaignSourceEntry getRelatedTarget(String fileName)
{
return new CampaignSourceEntry(campaign, uri.getRelatedTarget(fileName));
}
public List<Prerequisite> getPrerequisites()
{
return prerequisites;
}
}