//
// @(#)JudgeParser.java 1.00 6/2002
//
// Copyright 2002 Zachary DelProposto. All rights reserved.
// Use is subject to license terms.
//
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
// Or from http://www.gnu.org/
//
package dip.judge.parser;
import dip.world.Phase;
import dip.misc.Utils;
import dip.order.OrderFactory;
import java.io.*;
import java.util.regex.*;
import java.util.*;
/**
* First stage of Judge output parsing. Looks for the "::" line, determines
* game name, judge name, and variant type.
* <p>
* Then, looks to see (if present) the list of players and their email addresses.
* <p>
* Then, determines if input is a game listing or a game history. Sets flags, and
* if it is a game listing, returns the rest of the text. Determines if it is a
* game history by looking for a consecutive Date: / Subject: line pair
* <p>
* Parses the rest of the file into a String for sub-parsing.
*
*/
public class JudgeParser
{
// constants
private static final String JP_NO_COLONS = "JP.jp.nocolons";
// instance variables
private final static int READ_AHEAD_LENGTH = 7200;
private BufferedReader reader;
private OrderFactory orderFactory;
private String judgeName;
private String variantName;
private String gameName;
private Phase phase = null;
private String[] playerEmails;
private String[] playerNames;
private String text = null;
private String initialText = null;
public static final String JP_TYPE_LISTING = "Listing";
public static final String JP_TYPE_HISTORY = "History";
public static final String JP_TYPE_RESULTS = "Results";
public static final String JP_TYPE_GAMESTART = "Gamestart";
public static final String JP_TYPE_UNDEFINED = "Undefined";
private String type = JP_TYPE_UNDEFINED;
/** Create a JudgeParser, and start parsing. */
public JudgeParser(OrderFactory orderFactory, Reader r)
throws IOException, PatternSyntaxException
{
this.orderFactory = orderFactory;
reader = new BufferedReader(r, 8192);
findDoubleColonLine();
findPlayerList();
determineType();
}// JudgeParser()
/** Get the name of the Judge */
public String getJudgeName() { return judgeName; }
/** Get the name of the Variant */
public String getVariantName() { return variantName; }
/** Get the name of the Game */
public String getGameName() { return gameName; }
/** Get the phase of the game */
public Phase getPhase() { return phase; }
/** Returns the list of players in the game, or a zero-length-array */
public String[] getPlayerPowerNames() { return playerNames; }
/** Returns the email address for each player in the game, or a zero-length array */
public String[] getPlayerEmails() { return playerEmails; }
/** Returns the type of this input */
public String getType() { return type; }
/**
* Returns the rest of the text for further parsing.
* <p>
* This the "rest" of the text after parsing judge/player info; <br>
* if it is a history, it is every line INCLUDING and AFTER the first Date: line.
*
*/
public String getText() { return text; }
/** Prepend the given string in front of the stored text */
public String prependText(String s) { text = s + text; return text; }
/**
* For Listings, this is null. For Histories, this is the text after parsing game & player
* information but PRIOR to parsing Date: lines and turns. It is useful for some judges,
* where it will contain starting positions.
*
*/
public String getInitialText() { return initialText; }
/**
* Searches the file for the first matching line starting with "::" that
* has the Judge, Game, and Variant information. If these lines cannot be
* found, an IOException is thrown.
*
*/
private void findDoubleColonLine()
throws IOException, PatternSyntaxException
{
// regex pattern
// regex is case-insensitive
// capture groups are in order
// double () on last capture group because of buggy behavior!? WTF?
Pattern pattern = Pattern.compile("\\W*judge\\W*(\\S*)\\W*game\\W*(\\S*)\\W*variant\\W*((\\S*))", Pattern.CASE_INSENSITIVE);
// find :: line
String line = reader.readLine();
while(line != null)
{
if(line.trim().indexOf("::") >= 0)
{
// attempt to parse via regex. If it fails, read another line.
Matcher m = pattern.matcher(line);
if(m.find())
{
judgeName = m.group(1);
gameName = m.group(2);
variantName = m.group(3);
return;
}
}
line = reader.readLine();
}
// if we did not find any such line, indicate.
throw new IOException(Utils.getLocalString(JP_NO_COLONS));
}// findDoubleColonLine()
/**
* Searches the file for the player name list. The stream is marked, and
* is rewound if the list is not found. The list need not be present.
*/
private void findPlayerList()
throws IOException, PatternSyntaxException
{
// our pattern for finding the player list
Pattern pattern = Pattern.compile("(?i)following players");
reader.mark(READ_AHEAD_LENGTH);
int count = 0;
String line = reader.readLine();
while(line != null && count < READ_AHEAD_LENGTH)
{
count += line.length();
Matcher m = pattern.matcher(line);
if(m.find())
{
LinkedList names = new LinkedList();
LinkedList email = new LinkedList();
// now read each player UNTIL we get an empty line (or of length < 4)
line = reader.readLine();
while(line != null)
{
if(line.length() < 4)
{
break;
}
else
{
StringTokenizer st = new StringTokenizer(line, ": \t\n\r\f");
int nTok = st.countTokens();
if(nTok == 0)
{
break; // if line is somehow garbage....
}
for(int i=0; i<nTok; i++)
{
String tok = st.nextToken();
if(i == 0)
{
names.add(tok); // name is first token
}
else if(i == (nTok-1))
{
email.add(tok); // email is the last token
}
}
}
line = reader.readLine();
}
playerNames = (String[]) names.toArray(new String[names.size()]);
playerEmails = (String[]) email.toArray(new String[email.size()]);
return;
}
line = reader.readLine();
}
reader.reset();
playerEmails = new String[0];
playerNames = new String[0];
}// findPlayerList()
/**
* Determine if this is a listing or a history.
* History files will have a Date: and Subject: line; Listings will not.
*
* Resets mark as required
*/
private void determineType()
throws IOException, PatternSyntaxException
{
// are we are a history?
// we will also be reading lines in pairs.
String line = null;
reader.reset();
reader.mark(READ_AHEAD_LENGTH);
int count = 0;
// save the initial text; we will need this if we are a history;
// if we are a listing, this will be null. This is all the text UP TO and EXCLUDING
// the first "Date:" line.
StringBuffer initSB = new StringBuffer(2048);
line = reader.readLine();
while(line != null && count < READ_AHEAD_LENGTH)
{
// first check: Date
count += line.length();
int pos1 = line.toLowerCase().indexOf("date:");
if(pos1 >= 0 && pos1 < 10)
{
//System.out.println("Date Found");
// read next line; should be "subject" line
String line2 = reader.readLine();
if(line2 == null)
{
break;
}
count += line2.length();
// second check: Subject:
int pos2 = line2.toLowerCase().indexOf("subject:");
if(pos2 >= 0 && pos2 < 10)
{
type = JP_TYPE_HISTORY;
StringBuffer sb = new StringBuffer(256);
sb.append(line);
sb.append('\n');
sb.append(line2);
// set the rest of the text.
// prepend the already-parsed Date: and Subject: lines
initialText = initSB.toString();
makeRestOfText(sb.toString());
return;
}
}
else
{
// accumulate line
initSB.append(line);
initSB.append('\n');
}
line = reader.readLine();
}
// we are not a history.
// Next we try to find a result header.
Pattern hm = Pattern.compile(JudgeOrderParser.MOVE_ORDER_HEADER);
Pattern hr = Pattern.compile(JudgeOrderParser.RETREAT_ORDER_HEADER);
Pattern ha = Pattern.compile(JudgeOrderParser.ADJUSTMENT_ORDER_HEADER);
reader.reset();
count = 0;
line = reader.readLine();
while(line != null && count < READ_AHEAD_LENGTH)
{
count += line.length();
line = line.trim(); // needed for Patterns to work properly
Matcher m_hm = hm.matcher(line);
Matcher m_hr = hr.matcher(line);
Matcher m_ha = ha.matcher(line);
if(m_hm.lookingAt() ||
m_hr.lookingAt() ||
m_ha.lookingAt())
{
type = JP_TYPE_RESULTS;
phase = Phase.parse(line.substring(0,line.indexOf(".")));
break;
}
line = reader.readLine();
}
if (type == JP_TYPE_RESULTS)
{
// We are results. Get the text including the results header.
initialText = null;
reader.reset();
makeRestOfText(null);
return;
}
// Try to find a game starting message
Pattern gs = Pattern.compile(JudgeOrderParser.GAME_STARTING_HEADER);
Pattern sp = Pattern.compile(JudgeOrderParser.STARTING_POSITION_REGEX);
reader.reset();
count = 0;
line = reader.readLine();
while(line != null && count < READ_AHEAD_LENGTH)
{
count += line.length();
Matcher m_gs = gs.matcher(line);
Matcher m_sp = sp.matcher(line);
if (m_gs.lookingAt())
{
type = JP_TYPE_GAMESTART;
}
if (m_sp.lookingAt() && (type == JP_TYPE_GAMESTART))
{
phase = Phase.parse("Movement "+line.substring(0,line.indexOf(".")));
break;
}
line = reader.readLine();
}
if (type == JP_TYPE_GAMESTART)
{
// the game is starting. Get the text.
initialText = null;
reader.reset();
makeRestOfText(null);
return;
}
// Assume we are a listing. Get the text.
type = JP_TYPE_LISTING;
initialText = null;
reader.reset();
makeRestOfText(null);
}// determineType()
/** Given the current position in the reader, get the rest of the text to the end of the input */
private void makeRestOfText(String toPrepend)
throws IOException
{
StringBuffer sb = new StringBuffer(16384);
// prepend text first, if any
if(toPrepend != null)
{
sb.append(toPrepend);
sb.append('\n');
}
// read rest of text
String line = reader.readLine();
while(line != null)
{
sb.append(line);
sb.append('\n');
line = reader.readLine();
}
text = sb.toString();
}// makeRestOfText()
}// class JudgeParser