/*
Copyright (C) 2008,2009 Martin Günther <mintar@gmx.de>
This file is part of GgpRatingSystem.
GgpRatingSystem 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 3 of the License, or
(at your option) any later version.
GgpRatingSystem 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 GgpRatingSystem. If not, see <http://www.gnu.org/licenses/>.
*/
package ggpratingsystem;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.xpath.domapi.XPathEvaluatorImpl;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.xpath.XPathEvaluator;
import org.w3c.dom.xpath.XPathNSResolver;
import org.w3c.dom.xpath.XPathResult;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* Match.java
*
* This class is responsible for for reading
* match data from a file.
*/
public class FileMatchReader {
private static final Logger log = Logger.getLogger(FileMatchReader.class.getName());
static {
// inherit default level for package ggpratingsystem
log.setLevel(null);
}
private PlayerSet playerSet;
public FileMatchReader(PlayerSet playerSet) {
this.playerSet = playerSet;
}
public Match readMatch(MatchSet matchSet, File xmlFile) throws MatchParsingException {
// Data available in the XML source
String matchId;
List<Player> players;
List<Integer> scores;
log.fine("processing XML file: " + xmlFile);
try {
/* parse matchId */
XPathResult result = queryXPath(xmlFile, "/match/match-id");
Node firstResult = result.iterateNext();
if (firstResult != null) {
// competition 2007 XML format: there is a node called "match-id"
matchId = firstResult.getTextContent();
} else {
// competition 2008 XML format: "id" is an attribute of node "match"
result = queryXPath(xmlFile, "/match");
firstResult = result.iterateNext();
if (firstResult == null) {
throw new MatchParsingException("XPath query for match id returned no results!");
}
matchId = firstResult.getAttributes().getNamedItem("id").getTextContent();
}
if (result.iterateNext() != null) {
throw new MatchParsingException("XPath query for match id returned more than one result!");
}
log.finest("matchId: " + matchId);
/* parse roles */
result = queryXPath(xmlFile, "/match/role");
List<String> roles = new LinkedList<String>();
for (Node n = result.iterateNext(); n != null; n = result.iterateNext()) {
// n = <role>White</role>
Node firstChild = n.getFirstChild(); // firstChild = White
log.finest("Role: " + firstChild.getTextContent());
roles.add(firstChild.getTextContent());
if (n.getChildNodes().getLength() > 1) {
throw new MatchParsingException("XPath query for roles returned a node with several children!");
}
}
matchSet.getGame().setRoles(roles); // TODO What an ugly hack. Fix this in the future when roles of a game are available directly and not only via the matches.
/* parse players */
result = queryXPath(xmlFile, "/match/player");
players = new LinkedList<Player>();
for (Node n = result.iterateNext(); n != null; n = result.iterateNext()) {
// n = <player>FLUXPLAYER</player>
Node firstChild = n.getFirstChild(); // firstChild = FLUXPLAYER
log.finest("Player: " + firstChild.getTextContent());
players.add(playerSet.getPlayer(firstChild.getTextContent()));
if (n.getChildNodes().getLength() > 1) {
throw new MatchParsingException("XPath query for players returned a node with several children!");
}
}
/* parse scores */
scores = new LinkedList<Integer>();
result = queryXPath(xmlFile, "/match/scores/reward"); // 2007 XML format
List<Node> results = new LinkedList<Node>();
for (Node n = result.iterateNext(); n != null; n = result.iterateNext()) {
results.add(n);
}
if (results.size() == 0) {
result = queryXPath(xmlFile, "/match/rewards/reward"); // 2008 XML format
for (Node n = result.iterateNext(); n != null; n = result.iterateNext()) {
results.add(n);
}
}
for (Node n : results) {
// n = <reward>100</reward>
Node firstChild = n.getFirstChild(); // firstChild = 100
log.finest("Score: " + firstChild.getTextContent());
scores.add(Integer.valueOf(firstChild.getTextContent()));
if (n.getChildNodes().getLength() > 1) {
throw new MatchParsingException("XPath query for players returned a node with several children!");
}
}
// sanity check
if (roles.size() != players.size() || players.size() != scores.size() || roles.isEmpty()) {
throw new MatchParsingException("All 3 lists (roles, players, scores) must have the same number of elements and must not be empty!");
}
} catch (FileNotFoundException e) {
MatchParsingException thrown = new MatchParsingException("XML file was not found: " + xmlFile, e);
throw thrown;
} catch (SAXException e) {
MatchParsingException thrown = new MatchParsingException("SAXException while parsing XML file: " + xmlFile, e);
throw thrown;
} catch (IOException e) {
MatchParsingException thrown = new MatchParsingException("IOException while parsing XML file: " + xmlFile, e);
throw thrown;
} catch (ParserConfigurationException e) {
MatchParsingException thrown = new MatchParsingException("ParserConfigurationException while parsing XML file: " + xmlFile, e);
throw thrown;
}
return new Match(matchSet, matchId, players, scores);
}
private static XPathResult queryXPath(File xmlFile, String xpath) throws SAXException, IOException, ParserConfigurationException, MatchParsingException {
if (xmlFile == null || xpath == null || xpath.length() == 0) {
throw new MatchParsingException("Bad input arguments: " + xmlFile + ", " + xpath);
}
// Set up a DOM tree to query.
InputSource in = new InputSource(new FileInputStream(xmlFile));
DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
dfactory.setNamespaceAware(true);
// Disable loading of external DTDs. This has to be done in case that
// games.stanford.edu is down (again). Otherwise we'll get connection
// timeouts.
try {
dfactory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
} catch (ParserConfigurationException e) {
// Parser does not support this feature. We'll try to do without.
log.warning("XML Parser does not support disabling of feature load-external-dtd!");
}
Document doc = dfactory.newDocumentBuilder().parse(in);
// Create an XPath evaluator and pass in the document.
XPathEvaluator evaluator = new XPathEvaluatorImpl(doc);
XPathNSResolver resolver = evaluator.createNSResolver(doc);
// Evaluate the xpath expression
XPathResult result = (XPathResult) evaluator.evaluate(xpath, doc,
resolver, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
return result;
}
}