# HG changeset patch # User Jaroslav Tulach # Date 1251635867 -7200 # Node ID 69e897fe8140ef8a5d864f7831d18dac8fd5eeda # Parent 2b6c104e6a59364d365c140d1f2e78c05f45659d Spliting Game into GameId and Game with full info about the state of the game diff -r 2b6c104e6a59 -r 69e897fe8140 .hgignore --- a/.hgignore Sat Aug 29 16:15:20 2009 +0200 +++ b/.hgignore Sun Aug 30 14:37:47 2009 +0200 @@ -1,1 +1,4 @@ .*/target/.* +.*orig$ +.*~$ + diff -r 2b6c104e6a59 -r 69e897fe8140 freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/FreemarkerProcessor.java --- a/freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/FreemarkerProcessor.java Sat Aug 29 16:15:20 2009 +0200 +++ b/freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/FreemarkerProcessor.java Sun Aug 30 14:37:47 2009 +0200 @@ -34,8 +34,6 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; import javax.script.Bindings; import javax.script.ScriptContext; import javax.script.ScriptEngine; @@ -88,6 +86,7 @@ try { eng.eval(r); } catch (ScriptException ex) { + System.err.println("dump data: " + model); ex.printStackTrace(); throw new IOException(ex); } diff -r 2b6c104e6a59 -r 69e897fe8140 freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java --- a/freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java Sat Aug 29 16:15:20 2009 +0200 +++ b/freemarkerdor/src/main/java/cz/xelfi/quoridor/freemarkerdor/UI.java Sun Aug 30 14:37:47 2009 +0200 @@ -60,6 +60,7 @@ import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; +import org.w3c.dom.Document; /** * @@ -173,10 +174,6 @@ } - private static Object getJson(WebResource res) throws JSONException { - return convert(res.accept(MediaType.APPLICATION_JSON_TYPE).get(JSONArray.class)); - } - private static Object convert(Object obj) throws JSONException { if (obj instanceof JSONArray) { JSONArray arr = (JSONArray)obj; @@ -226,7 +223,8 @@ } private Viewable welcomeImpl() throws JSONException { - Object obj = getJson(base.path("games")); + final Object got = base.path("games").accept(MediaType.TEXT_XML_TYPE).get(Document.class); + Map obj = (Map)convert(got); return new Viewable("index.fmt", obj); } diff -r 2b6c104e6a59 -r 69e897fe8140 freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/game.fmt --- a/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/game.fmt Sat Aug 29 16:15:20 2009 +0200 +++ b/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/game.fmt Sun Aug 30 14:37:47 2009 +0200 @@ -6,6 +6,7 @@

Game

+

${id.white} vs. ${id.black}

-
${model}
+
${board}
\ No newline at end of file diff -r 2b6c104e6a59 -r 69e897fe8140 freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/index.fmt --- a/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/index.fmt Sat Aug 29 16:15:20 2009 +0200 +++ b/freemarkerdor/src/main/resources/cz/xelfi/quoridor/freemarkerdor/UI/index.fmt Sun Aug 30 14:37:47 2009 +0200 @@ -8,7 +8,7 @@

Quoridor Community Server

    - <#list model as item> + <#list gameId as item>
  1. ${item.white} vs. ${item.black} board
diff -r 2b6c104e6a59 -r 69e897fe8140 quoridor/src/main/java/cz/xelfi/quoridor/Board.java --- a/quoridor/src/main/java/cz/xelfi/quoridor/Board.java Sat Aug 29 16:15:20 2009 +0200 +++ b/quoridor/src/main/java/cz/xelfi/quoridor/Board.java Sun Aug 30 14:37:47 2009 +0200 @@ -31,6 +31,7 @@ import java.io.EOFException; import java.io.IOException; import java.io.Reader; +import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; @@ -293,6 +294,15 @@ // private static final Pattern northSouthPattern = Pattern.compile("(\\+(\\|*)(\\*)?-+\\+)"); + + /** Reads the board from a reader. Opposite operation to {@link #write(java.io.Writer)}. + * + * @param r the reader + * @return the read board + * @throws IOException if I/O error occurs + * @throws IllegalPositionException if the reader does not contain description + * of the board + */ public static Board read(Reader r) throws IOException, IllegalPositionException { BufferedReader b = new BufferedReader(r); for (;;) { @@ -307,6 +317,24 @@ } } + /** Translates the string into board, if possible. String created by + * use of {@link #toString()} is accepted, more information about the + * format is avaliable in the description of {@link #write(java.io.Writer)} + * method. + * + * @param board string to analyze + * @return board object, if the string can be read + * @throws IllegalPositionException if the string does not represent the board + */ + public static Board valueOf(String board) throws IllegalPositionException { + try { + return read(new StringReader(board)); + } catch (IOException ex) { + // shall not happen, StringReader does not throw IOException + throw (IllegalPositionException)new IllegalPositionException(ex.getMessage()).initCause(ex); + } + } + private static int assertChar(String s, int pos, char... ch) throws IOException { if (s.length() >= pos) { for (int i = 0; i < ch.length; i++) { @@ -635,6 +663,12 @@ return false; } + /** Converts the board into string representation. For more information + * about the format see {@link #write(java.io.Writer)}. To read the + * string back use {@link #valueOf(java.lang.String)}. + * + * @return string representing the board + */ @Override public String toString() { StringWriter w = new StringWriter(); diff -r 2b6c104e6a59 -r 69e897fe8140 quoridor/src/test/java/cz/xelfi/quoridor/SerializeTest.java --- a/quoridor/src/test/java/cz/xelfi/quoridor/SerializeTest.java Sat Aug 29 16:15:20 2009 +0200 +++ b/quoridor/src/test/java/cz/xelfi/quoridor/SerializeTest.java Sun Aug 30 14:37:47 2009 +0200 @@ -72,7 +72,7 @@ StringWriter w = new StringWriter(); b.write(w); w.close(); - return Board.read(new StringReader(w.toString())); + return Board.valueOf(w.toString()); } } diff -r 2b6c104e6a59 -r 69e897fe8140 webidor/src/main/java/cz/xelfi/quoridor/webidor/Game.java --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/Game.java Sat Aug 29 16:15:20 2009 +0200 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/Game.java Sun Aug 30 14:37:47 2009 +0200 @@ -32,12 +32,13 @@ import cz.xelfi.quoridor.Player; import java.util.ArrayList; import java.util.List; -import java.util.UUID; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; -import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; /** * @@ -45,45 +46,31 @@ */ @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) -public final class Game { +public final class Game extends Object { + @XmlElement + private GameId id; @XmlAttribute - private String white; - @XmlAttribute - private String black; - @XmlID - private String id; - - private transient Board board; - private transient List moves; + @XmlJavaTypeAdapter(BoardAdapter.class) + private Board board; + @XmlElement + @XmlJavaTypeAdapter(MoveAdapter.class) + private List moves; Game() { } public Game(String first, String second) { - this( - UUID.randomUUID().toString(), - first, second - ); + this.id = new GameId(first, second); } - public Game(String id, String first, String second) { - this.white = first; - this.black = second; + public Game(GameId id) { this.id = id; } - public String getId() { + public GameId getId() { return id; } - - public String getWhite() { - return white; - } - - public String getBlack() { - return black; - } - + public Board getBoard() { if (board == null) { board = Board.empty(); @@ -93,10 +80,10 @@ public void apply(String player, Move m) throws IllegalPositionException { Player p = null; - if (getWhite().equals(player)) { + if (id.getWhite().equals(player)) { p = getBoard().getPlayers().get(0); } else { - if (getBlack().equals(player)) { + if (id.getBlack().equals(player)) { p = getBoard().getPlayers().get(1); } } @@ -114,4 +101,38 @@ } return moves; } + + private static final class MoveAdapter extends XmlAdapter> { + @Override + public List unmarshal(String[] arr) throws Exception { + List res = new ArrayList(); + for (String v : arr) { + res.add(Move.valueOf(v)); + } + return res; + } + + @Override + public String[] marshal(List arr) throws Exception { + List res = new ArrayList(); + for (Move m : arr) { + res.add(m.toString()); + } + return res.toArray(new String[0]); + } + } // end of MoveAdapter + + private static final class BoardAdapter extends XmlAdapter { + + @Override + public Board unmarshal(String v) throws Exception { + return Board.valueOf(v); + } + + @Override + public String marshal(Board v) throws Exception { + return v.toString(); + } + + } } diff -r 2b6c104e6a59 -r 69e897fe8140 webidor/src/main/java/cz/xelfi/quoridor/webidor/GameId.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/GameId.java Sun Aug 30 14:37:47 2009 +0200 @@ -0,0 +1,92 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * Portions Copyrighted 2009 Jaroslav Tulach + */ + +package cz.xelfi.quoridor.webidor; + +import java.util.Date; +import java.util.UUID; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlID; +import javax.xml.bind.annotation.XmlRootElement; + +/** Basic identification of a game. + * + * @author Jaroslav Tulach + */ +@XmlRootElement +@XmlAccessorType(XmlAccessType.FIELD) +public class GameId { + @XmlAttribute + private String white; + @XmlAttribute + private String black; + @XmlAttribute + private Date started; + @XmlAttribute + private GameResult result; + @XmlID + private String id; + + GameId() { + } + + public GameId(String first, String second) { + this( + UUID.randomUUID().toString(), + first, second, new Date(), GameResult.IN_PROGRESS + ); + } + + public GameId(String id, String first, String second, Date started, GameResult result) { + this.white = first; + this.black = second; + this.id = id; + this.started = started; + this.result = result; + } + + public String getId() { + return id; + } + + public String getWhite() { + return white; + } + + public String getBlack() { + return black; + } + + public Date getStarted() { + return started; + } + + public GameResult getResult() { + return result; + } +} diff -r 2b6c104e6a59 -r 69e897fe8140 webidor/src/main/java/cz/xelfi/quoridor/webidor/GameResult.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/GameResult.java Sun Aug 30 14:37:47 2009 +0200 @@ -0,0 +1,35 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * Contributor(s): + * + * Portions Copyrighted 2009 Jaroslav Tulach + */ + +package cz.xelfi.quoridor.webidor; + +/** Possible results of the game. + * + * @author Jaroslav Tulach + */ +public enum GameResult { + IN_PROGRESS, WHITE_WON, BLACK_WON; +} diff -r 2b6c104e6a59 -r 69e897fe8140 webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java Sat Aug 29 16:15:20 2009 +0200 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java Sun Aug 30 14:37:47 2009 +0200 @@ -37,11 +37,8 @@ import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; +import java.util.Date; import java.util.List; -import java.util.Map; -import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.ws.rs.GET; @@ -52,14 +49,13 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; -import org.codehaus.jettison.json.JSONObject; /** * * @author Jaroslav Tulach */ public final class Games { - private List games = new ArrayList(); + private final List games = new ArrayList(); private final File dir; private static final Logger LOG = Logger.getLogger(Games.class.getName()); @@ -79,11 +75,11 @@ } @POST - @Produces(MediaType.APPLICATION_JSON) - public Game createGame( + @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) + public GameId createGame( @QueryParam("white") String white, @QueryParam("black") String black - ) { + ) throws IOException { if (white == null) { throw new IllegalArgumentException("Must specify white"); } @@ -91,8 +87,9 @@ throw new IllegalArgumentException("Must specify black"); } Game g = new Game(white, black); + storeGame(g); games.add(g); - return g; + return g.getId(); } @GET @@ -108,22 +105,19 @@ @GET @Path("{id}") - @Produces(MediaType.APPLICATION_JSON) - public Object getBoardInfo(@PathParam("id") String id) { + @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) + public Game getBoardInfo(@PathParam("id") String id) { Game g = findGame(id); if (g == null) { throw new IllegalArgumentException("Unknown game " + id); } - Map data = new HashMap(); - data.put("board", g.getBoard().toString()); - data.put("game", g); - return data; + return g; } @PUT @Path("{id}") - @Produces(MediaType.APPLICATION_JSON) - public Game applyMove( + @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) + public GameId applyMove( @PathParam("id") String id, @QueryParam("player") String player, @QueryParam("move") String move @@ -139,18 +133,26 @@ } catch (IOException ex) { LOG.log(Level.WARNING, "Cannot store game " + id, ex); } - return g; + return g.getId(); } @GET - @Produces(MediaType.APPLICATION_JSON) + @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_XML }) + public List listGames() { + List arr = new ArrayList(games.size()); + for (Game g : games) { + arr.add(g.getId()); + } + return arr; + } + public List getGames() { return games; } private Game findGame(String id) { for (Game g : games) { - if (g.getId().equals(id)) { + if (g.getId().getId().equals(id)) { return g; } } @@ -165,7 +167,7 @@ for (;;) { String line = r.readLine(); if (line == null) { - break; + line = "finish"; } line = line.trim(); if (line.length() == 0) { @@ -186,7 +188,11 @@ throw new IOException("Missing white and black identification in " + f); } if (g == null) { - g = new Game(f.getName(), white, black); + GameId id = new GameId(f.getName(), white, black, new Date(f.lastModified()), GameResult.IN_PROGRESS); + g = new Game(id); + } + if (line.equals("finish")) { + break; } String[] moves = line.split(" "); if (moves.length == 0) { @@ -212,10 +218,11 @@ private void storeGame(Game g) throws IOException { dir.mkdirs(); - File f = new File(dir, g.getId()); + File f = new File(dir, g.getId().getId()); PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(f))); - pw.println("# white: " + g.getWhite()); - pw.println("# black: " + g.getBlack()); + pw.println("# white: " + g.getId().getWhite()); + pw.println("# black: " + g.getId().getBlack()); + pw.println("# status: " + g.getId().getResult()); int cnt = 0; for (Move m : g.getMoves()) { pw.print(m.toString()); diff -r 2b6c104e6a59 -r 69e897fe8140 webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Quoridor.java --- a/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Quoridor.java Sat Aug 29 16:15:20 2009 +0200 +++ b/webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Quoridor.java Sun Aug 30 14:37:47 2009 +0200 @@ -33,8 +33,6 @@ import com.sun.net.httpserver.HttpServer; import java.io.File; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import javax.ws.rs.Path; /** diff -r 2b6c104e6a59 -r 69e897fe8140 webidor/src/test/java/cz/xelfi/quoridor/webidor/QuoridorTest.java --- a/webidor/src/test/java/cz/xelfi/quoridor/webidor/QuoridorTest.java Sat Aug 29 16:15:20 2009 +0200 +++ b/webidor/src/test/java/cz/xelfi/quoridor/webidor/QuoridorTest.java Sun Aug 30 14:37:47 2009 +0200 @@ -33,14 +33,12 @@ import cz.xelfi.quoridor.Board; import cz.xelfi.quoridor.Move; import cz.xelfi.quoridor.webidor.resources.Games; -import cz.xelfi.quoridor.webidor.resources.Quoridor; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.List; import java.util.Map; import javax.ws.rs.core.MediaType; -import org.codehaus.jettison.json.JSONObject; import org.junit.Test; import static org.junit.Assert.*; @@ -94,33 +92,33 @@ @Test public void testCreateAGame() throws Exception { webResource = webResource.path("api"); - Game s = webResource.path("games").queryParam("white", "Jarda") - .queryParam("black", "Jirka").post(Game.class); + GameId s = webResource.path("games").queryParam("white", "Jarda") + .queryParam("black", "Jirka").post(GameId.class); String msg = webResource.path("games").get(String.class); //List games = webResource.path("games").get(new GenericType>() {}); - GenericType> gType = new GenericType>() {}; + GenericType> gType = new GenericType>() {}; - List games = webResource.path("games").accept("application/json").get(gType); + List games = webResource.path("games").accept("application/json").get(gType); assertEquals("One game", 1, games.size()); assertEquals("Same white", "Jarda", games.get(0).getWhite()); assertEquals("Same black", "Jirka", games.get(0).getBlack()); - Game s1 = webResource.path("games/" + s.getId()).queryParam("player", "Jarda").queryParam("move", "N").put(Game.class); + GameId s1 = webResource.path("games/" + s.getId()).queryParam("player", "Jarda").queryParam("move", "N").put(GameId.class); try { - Game s2 = webResource.path("games/" + s.getId()).queryParam("player", "Jarda").queryParam("move", "N").put(Game.class); + GameId s2 = webResource.path("games/" + s.getId()).queryParam("player", "Jarda").queryParam("move", "N").put(GameId.class); fail("Not Jarda's turn, previous call shall fail"); } catch (UniformInterfaceException ex) { // OK } try { - Game s2 = webResource.path("games/" + s.getId()).queryParam("player", "Jirka").queryParam("move", "NONSENCE").put(Game.class); + GameId s2 = webResource.path("games/" + s.getId()).queryParam("player", "Jirka").queryParam("move", "NONSENCE").put(GameId.class); fail("Invalid move"); } catch (UniformInterfaceException ex) { // OK } - Game s2 = webResource.path("games/" + s.getId()).queryParam("player", "Jirka").queryParam("move", "S").put(Game.class); + GameId s2 = webResource.path("games/" + s.getId()).queryParam("player", "Jirka").queryParam("move", "S").put(GameId.class); assertNotNull("Successful move", s2); File game = new File(new File(dir, "games"), s2.getId()); @@ -144,22 +142,24 @@ Games read = new Games(new File(dir, "games")); List readGames = read.getGames(); assertEquals("One game read", 1, readGames.size()); - Board board = read.getGames().get(0).getBoard(); + Board board = readGames.get(0).getBoard(); assertEquals(1, board.getPlayers().get(0).getRow()); assertEquals(7, board.getPlayers().get(1).getRow()); - assertEquals(Move.NORTH, read.getGames().get(0).getMoves().get(0)); - assertEquals(Move.SOUTH, read.getGames().get(0).getMoves().get(1)); + assertEquals(Move.NORTH, readGames.get(0).getMoves().get(0)); + assertEquals(Move.SOUTH, readGames.get(0).getMoves().get(1)); class GMap extends GenericType>{} - JSONObject map = webResource.path("games").path(s.getId()).accept(MediaType.APPLICATION_JSON).get(JSONObject.class); - assertNotNull("Map really returned", map); - String txtBoard = (String) map.get("board"); - assertNotNull("Contains its textual form", txtBoard); - assertEquals("It is same as text of our game", board.toString(), txtBoard); + String text = webResource.path("games").path(s.getId()).accept(MediaType.TEXT_PLAIN).get(String.class); + if (text.indexOf("-----") == -1) { + fail("Expecting board:\n" + text); + } + Game readGame = webResource.path("games").path(s.getId()).accept(MediaType.TEXT_XML).get(Game.class); + assertNotNull("Game really returned", readGame); + assertEquals("Same game as in text representation", readGame.getBoard(), Board.valueOf(text)); + assertEquals("It is same as text of our game", readGame.getBoard().toString(), text); - Object og = map.getJSONObject("game"); - assertTrue("Instance of JSON: " + og, og instanceof JSONObject); - JSONObject jg = (JSONObject)og; + assertEquals(Move.NORTH, readGame.getMoves().get(0)); + assertEquals(Move.SOUTH, readGame.getMoves().get(1)); } }