/*
* PCClassLoader.java
* Copyright 2001 (C) Bryan McRoberts <merton_monk@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
*
* Created on February 22, 2002, 10:29 PM
*
* $Id$
*/
package pcgen.persistence.lst;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
import pcgen.cdom.enumeration.IntegerKey;
import pcgen.cdom.enumeration.ListKey;
import pcgen.cdom.enumeration.ObjectKey;
import pcgen.cdom.inst.PCClassLevel;
import pcgen.core.PCClass;
import pcgen.core.SubClass;
import pcgen.core.SubstitutionClass;
import pcgen.core.analysis.PCClassKeyChange;
import pcgen.persistence.PersistenceLayerException;
import pcgen.persistence.SystemLoader;
import pcgen.persistence.lst.utils.DeferredLine;
import pcgen.rules.context.LoadContext;
import pcgen.util.Logging;
/**
*
* @author David Rice <david-pcgen@jcuz.com>
*/
public final class PCClassLoader extends LstObjectFileLoader<PCClass>
{
/**
* @see pcgen.persistence.lst.LstObjectFileLoader#parseLine(LoadContext, pcgen.cdom.base.CDOMObject, String, SourceEntry)
*/
@Override
public PCClass parseLine(LoadContext context, PCClass target,
String lstLine, SourceEntry source) throws PersistenceLayerException
{
if (lstLine.startsWith("SUBCLASS:")
|| lstLine.startsWith("SUBCLASSLEVEL:"))
{
if (target == null)
{
Logging.errorPrint("Ignoring line: " + lstLine
+ " as SUBCLASS* type line appeared before CLASS: line");
return null;
}
SubClass subClass = null;
if (lstLine.startsWith("SUBCLASS:"))
{
int tabLoc = lstLine.indexOf("\t");
if (tabLoc == -1)
{
Logging.errorPrint("Expected SUBCLASS to have "
+ "additional Tags in " + source.getURI()
+ " (e.g. COST is a required Tag in a SUBCLASS)");
}
final String n = lstLine.substring(9, tabLoc);
String restOfLine = lstLine.substring(tabLoc);
subClass = target.getSubClassKeyed(n);
if (subClass == null)
{
subClass = new SubClass();
subClass.setName(n.intern());
subClass.put(ObjectKey.SOURCE_CAMPAIGN, source.getCampaign());
subClass.setSourceURI(source.getURI());
target.addSubClass(subClass);
}
parseLineIntoClass(context, subClass, source, restOfLine);
}
else
{
List<SubClass> subClassList = target.getListFor(ListKey.SUB_CLASS);
if (subClassList != null)
{
subClass = subClassList.get(subClassList.size() - 1);
subClass.addToListFor(ListKey.SUB_CLASS_LEVEL, new DeferredLine(source, lstLine.substring(14).intern()));
}
}
return target;
}
if (lstLine.startsWith("SUBSTITUTIONCLASS:")
|| lstLine.startsWith("SUBSTITUTIONLEVEL:"))
{
if (target == null)
{
Logging.errorPrint("Ignoring line: " + lstLine
+ " as SUBSTITUTIONCLASS* type line appeared before CLASS: line");
return null;
}
SubstitutionClass substitutionClass = null;
if (lstLine.startsWith("SUBSTITUTIONCLASS:"))
{
int tabLoc = lstLine.indexOf("\t");
String name;
String restOfLine;
if (tabLoc > 0)
{
name = lstLine.substring(18, tabLoc);
restOfLine = lstLine.substring(tabLoc);
}
else
{
name = lstLine.substring(18);
restOfLine = null;
}
substitutionClass = target.getSubstitutionClassKeyed(name);
if (substitutionClass == null)
{
substitutionClass = new SubstitutionClass();
substitutionClass.setName(name.intern());
substitutionClass.put(ObjectKey.SOURCE_CAMPAIGN, source.getCampaign());
substitutionClass.setSourceURI(source.getURI());
target.addSubstitutionClass(substitutionClass);
}
parseLineIntoClass(context, substitutionClass, source, restOfLine);
}
else
{
if (lstLine.indexOf('\t') == -1)
{
Logging.errorPrint("Ignoring line: " + lstLine
+ " as SUBSTITUTIONLEVEL line was empty");
return null;
}
List<SubstitutionClass> substitutionClassList = target
.getListFor(ListKey.SUBSTITUTION_CLASS);
if (substitutionClassList != null
&& !substitutionClassList.isEmpty()
&& lstLine.length() > 18)
{
substitutionClass = substitutionClassList
.get(substitutionClassList.size() - 1);
substitutionClass.addToListFor(ListKey.SUB_CLASS_LEVEL, new DeferredLine(source, lstLine.substring(18).intern()));
}
}
return target;
}
return parseClassLine(context, lstLine, source, target);
}
private PCClass parseClassLine(LoadContext context, String lstLine,
SourceEntry source, PCClass pcClass)
throws PersistenceLayerException
{
int tabLoc = lstLine.indexOf(SystemLoader.TAB_DELIM);
String lineIdentifier;
String restOfLine;
if (tabLoc == -1)
{
lineIdentifier = lstLine;
restOfLine = null;
}
else
{
lineIdentifier = lstLine.substring(0, tabLoc);
restOfLine = lstLine.substring(tabLoc + 1);
}
if (lineIdentifier.startsWith("CLASS:"))
{
String name = lineIdentifier.substring(6);
if (pcClass == null || !name.equals(pcClass.getKeyName())
&& (name.indexOf(".MOD") < 0))
{
if (pcClass != null)
{
completeObject(context, source, pcClass);
}
pcClass = new PCClass();
pcClass.setName(name.intern());
pcClass.setSourceURI(source.getURI());
pcClass.put(ObjectKey.SOURCE_CAMPAIGN, source.getCampaign());
context.addStatefulInformation(pcClass);
context.getReferenceContext().importObject(pcClass);
}
// need to grab PCClass instance for this .MOD minus the .MOD part of the name
else if (name.endsWith(".MOD"))
{
pcClass =
context.getReferenceContext().silentlyGetConstructedCDOMObject(PCClass.class, name.substring(0, name
.length() - 4).intern());
}
parseLineIntoClass(context, pcClass, source, restOfLine);
}
else
{
parseFullClassLevelLine(context, source, pcClass, lineIdentifier,
restOfLine);
}
return pcClass;
}
private void parseFullClassLevelLine(LoadContext context,
SourceEntry source, PCClass pcClass, String lineIdentifier,
String restOfLine) throws PersistenceLayerException
{
try
{
String repeatTag = null;
String thisLevel;
int rlLoc = lineIdentifier.indexOf(":REPEATLEVEL:");
if (rlLoc == -1)
{
thisLevel = lineIdentifier;
}
else
{
thisLevel = lineIdentifier.substring(0, rlLoc);
repeatTag = lineIdentifier.substring(rlLoc + 13);
}
int iLevel = Integer.parseInt(thisLevel);
if (iLevel == 0)
{
/*
* This is for backwards compatibility with PCGen 5.14
* getPCCText which writes out things to level 0 :P
*/
parseLineIntoClass(context, pcClass, source, restOfLine);
}
else if (iLevel > 0)
{
parseClassLevelLine(context, pcClass, iLevel, source, restOfLine);
if (repeatTag != null)
{
parseRepeatClassLevel(context, restOfLine, source, pcClass, iLevel, repeatTag);
}
}
else
{
Logging.errorPrint("Invalid Level Identifier: " + thisLevel
+ " for " + pcClass.getDisplayName()
+ ". Value must be greater than zero");
}
}
catch (NumberFormatException nfe)
{
// I think we can ignore this, as
// it's supposed to be the level #
// but could be almost anything else
Logging.errorPrint("Expected a level value, but got '"
+ lineIdentifier + "' instead (as a level line in "
+ (pcClass == null ? "no class" : pcClass.getKeyName())
+ ") in source " + source.getURI());
Logging.errorPrint(" Rest of line was: " + restOfLine);
}
}
public void parseClassLevelLine(LoadContext context, PCClass pcClass,
int lvl, SourceEntry source,
String restOfLine) throws PersistenceLayerException
{
if (restOfLine == null)
{
return;
}
PCClassLevel classlevel = pcClass.getOriginalClassLevel(lvl);
final StringTokenizer colToken = new StringTokenizer(restOfLine,
SystemLoader.TAB_DELIM);
// loop through all the tokens and parse them
while (colToken.hasMoreTokens())
{
String token = colToken.nextToken().trim();
int colonLoc = token.indexOf(':');
if (colonLoc == -1)
{
Logging
.errorPrint("Invalid Token - does not contain a colon: '"
+ token
+ "' in Class "
+ pcClass.getDisplayName() + " of " + source);
continue;
}
else if (colonLoc == 0)
{
Logging.errorPrint("Invalid Token - starts with a colon: '"
+ token + "' in Class " + pcClass.getDisplayName()
+ " of " + source);
continue;
}
String key = token.substring(0, colonLoc);
String value = (colonLoc == token.length() - 1) ? null : token
.substring(colonLoc + 1);
if (context.processToken(classlevel, key.intern(), value.intern()))
{
context.commit();
}
else
{
context.rollback();
Logging.replayParsedMessages();
}
Logging.clearParseMessages();
}
}
public void parseLineIntoClass(LoadContext context, PCClass pcClass,
SourceEntry source, String restOfLine) throws PersistenceLayerException
{
if (restOfLine == null)
{
return;
}
final StringTokenizer colToken =
new StringTokenizer(restOfLine, SystemLoader.TAB_DELIM);
// loop through all the tokens and parse them
while (colToken.hasMoreTokens())
{
String token = colToken.nextToken().trim();
int colonLoc = token.indexOf(':');
if (colonLoc == -1)
{
Logging
.errorPrint("Invalid Token - does not contain a colon: '"
+ token
+ "' in Class "
+ pcClass.getDisplayName() + " of " + source);
continue;
}
else if (colonLoc == 0)
{
Logging.errorPrint("Invalid Token - starts with a colon: '"
+ token + "' in Class " + pcClass.getDisplayName()
+ " of " + source);
continue;
}
String key = token.substring(0, colonLoc);
String value = (colonLoc == token.length() - 1) ? null : token
.substring(colonLoc + 1);
if (context.processToken(pcClass, key.intern(), value.intern()))
{
context.commit();
}
else
{
context.rollback();
Logging.replayParsedMessages();
}
Logging.clearParseMessages();
}
}
private void parseRepeatClassLevel(LoadContext context, String restOfLine,
SourceEntry source, PCClass pcClass, int iLevel,
String colString) throws PersistenceLayerException
{
//
// REPEAT:<level increment>|<consecutive>|<max level>
//
final StringTokenizer repeatToken = new StringTokenizer(colString, "|");
final int tokenCount = repeatToken.countTokens();
int lvlIncrement = 1000; // an arbitrarily large number...
int consecutive = 0; // 0 means don't skip any
int maxLevel = 100; // an arbitrarily large number...
if (pcClass.hasMaxLevel())
{
maxLevel = pcClass.getSafe(IntegerKey.LEVEL_LIMIT);
}
if (tokenCount > 0)
{
try
{
lvlIncrement = Integer.parseInt(repeatToken.nextToken());
}
catch (NumberFormatException nfe)
{
Logging.errorPrint("Non-Numeric Level Increment info '"
+ colString + "' in " + source.getURI(), nfe);
}
}
boolean oldSyntax = false;
if (tokenCount > 1)
{
boolean consumed = false;
String tokenTwo = repeatToken.nextToken();
if (tokenTwo.startsWith("SKIP="))
{
tokenTwo = tokenTwo.substring(5);
}
else if (tokenTwo.startsWith("MAX="))
{
if (tokenCount > 2)
{
Logging.errorPrint("MAX= cannot be followed by another item in REPEATLEVEL. SKIP= must appear before MAX=");
}
String maxString = tokenTwo.substring(4);
try
{
maxLevel = Integer.parseInt(maxString);
}
catch (NumberFormatException nfe)
{
Logging.errorPrint("Non-Numeric Max Level info MAX='" + maxLevel
+ "' in " + source.getURI(), nfe);
}
consumed = true;
}
else
{
oldSyntax = true;
}
if (!consumed)
{
try
{
consecutive = Integer.parseInt(tokenTwo);
}
catch (NumberFormatException nfe)
{
Logging.errorPrint("Non-Numeric Consecutive Level info '"
+ colString + "' in " + source.getURI(), nfe);
}
}
}
if (tokenCount > 2)
{
String tokenThree = repeatToken.nextToken();
String maxString;
if (!oldSyntax && tokenThree.startsWith("MAX="))
{
maxString = tokenThree.substring(4);
}
else
{
maxString = tokenThree;
}
try
{
maxLevel = Integer.parseInt(maxString);
}
catch (NumberFormatException nfe)
{
Logging.errorPrint("Non-Numeric Max Level info '" + colString
+ "' in " + source.getURI(), nfe);
}
}
int count = consecutive - 1; // first one already added by processing of lstLine, so skip it
for (int lvl = iLevel + lvlIncrement; lvl <= maxLevel; lvl +=
lvlIncrement)
{
if ((consecutive == 0) || (count != 0))
{
parseClassLevelLine(context, pcClass, lvl, source, restOfLine);
}
if (consecutive != 0)
{
if (count == 0)
{
count = consecutive;
}
else
{
--count;
}
}
}
}
/**
* @see pcgen.persistence.lst.LstObjectFileLoader#getObjectKeyed(LoadContext, String)
*/
@Override
protected PCClass getObjectKeyed(LoadContext context, String aKey)
{
return context.getReferenceContext().silentlyGetConstructedCDOMObject(PCClass.class, aKey.startsWith("CLASS:") ? aKey
.substring(6) : aKey);
}
public void loadSubLines(LoadContext context)
{
Collection<PCClass> allClasses = context.getReferenceContext()
.getConstructedCDOMObjects(PCClass.class);
for (PCClass cl : allClasses)
{
List<SubClass> subClasses = cl.getListFor(ListKey.SUB_CLASS);
if (subClasses != null)
{
for (SubClass sc : subClasses)
{
sc.copyLevelsFrom(cl);
processSubLevelLines(context, cl, sc);
}
}
List<SubstitutionClass> substClasses = cl.getListFor(ListKey.SUBSTITUTION_CLASS);
if (substClasses != null)
{
for (SubstitutionClass sc : substClasses)
{
processSubLevelLines(context, cl, sc);
}
}
}
}
private void processSubLevelLines(LoadContext context, PCClass cl,
PCClass sc)
{
for (DeferredLine dl : sc.getSafeListFor(ListKey.SUB_CLASS_LEVEL))
{
context.setSourceURI(dl.source.getURI());
String lstLine = dl.lstLine;
try
{
int tabLoc = lstLine.indexOf(SystemLoader.TAB_DELIM);
String lineIdentifier;
String restOfLine;
if (tabLoc == -1)
{
lineIdentifier = lstLine;
restOfLine = null;
}
else
{
lineIdentifier = lstLine.substring(0, tabLoc);
restOfLine = lstLine.substring(tabLoc + 1);
}
parseFullClassLevelLine(context, dl.source, sc, lineIdentifier, restOfLine);
}
catch (PersistenceLayerException ple)
{
Logging.log(Logging.LST_ERROR,
"Error parsing " + sc.getClass().getSimpleName() + " line: "
+ cl.getKeyName() + " "
+ sc.getKeyName() + " " + lstLine, ple);
}
}
}
@Override
public PCClass getCopy(LoadContext context, String baseName,
String copyName, CampaignSourceEntry source) throws PersistenceLayerException
{
PCClass copy = super.getCopy(context, baseName, copyName, source);
PCClassKeyChange.changeReferences(baseName, copy);
return copy;
}
}