webidor/src/main/java/cz/xelfi/quoridor/webidor/resources/Games.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 14 Sep 2010 08:56:13 +0200
changeset 264 d60370059c3c
parent 238 a4f6aca595e8
permissions -rw-r--r--
Changing headers to GPLv3
     1 /*
     2  * Quoridor server and related libraries
     3  * Copyright (C) 2009-2010 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, either version 3 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://www.gnu.org/licenses/.
    17  */
    18 
    19 package cz.xelfi.quoridor.webidor.resources;
    20 
    21 import com.sun.jersey.api.json.JSONWithPadding;
    22 import cz.xelfi.quoridor.IllegalPositionException;
    23 import cz.xelfi.quoridor.webidor.*;
    24 import cz.xelfi.quoridor.Move;
    25 import java.io.BufferedReader;
    26 import java.io.BufferedWriter;
    27 import java.io.File;
    28 import java.io.FileInputStream;
    29 import java.io.FileOutputStream;
    30 import java.io.IOException;
    31 import java.io.InputStream;
    32 import java.io.InputStreamReader;
    33 import java.io.OutputStreamWriter;
    34 import java.io.PrintWriter;
    35 import java.io.Writer;
    36 import java.util.ArrayList;
    37 import java.util.Collections;
    38 import java.util.Date;
    39 import java.util.List;
    40 import java.util.logging.Level;
    41 import java.util.logging.Logger;
    42 import java.util.regex.Matcher;
    43 import java.util.regex.Pattern;
    44 import javax.ws.rs.DefaultValue;
    45 import javax.ws.rs.GET;
    46 import javax.ws.rs.POST;
    47 import javax.ws.rs.PUT;
    48 import javax.ws.rs.Path;
    49 import javax.ws.rs.PathParam;
    50 import javax.ws.rs.Produces;
    51 import javax.ws.rs.QueryParam;
    52 import javax.ws.rs.WebApplicationException;
    53 import javax.ws.rs.core.GenericEntity;
    54 import javax.ws.rs.core.MediaType;
    55 import javax.ws.rs.core.Response.Status;
    56 
    57 /**
    58  *
    59  * @author Jaroslav Tulach <jtulach@netbeans.org>
    60  */
    61 public final class Games {
    62     private final Quoridor quoridor;
    63     private final List<Game> games = new ArrayList<Game>();
    64     private final File dir;
    65     private static final Logger LOG = Logger.getLogger(Games.class.getName());
    66 
    67     public Games(File dir, Quoridor quoridor) {
    68         this.dir = dir;
    69         this.quoridor = quoridor;
    70         File[] arr = dir.listFiles();
    71         if (arr != null) {
    72             for (File f : arr) {
    73                 try {
    74                     Game g = readGame(f);
    75                     games.add(g);
    76                 } catch (IOException ex) {
    77                     LOG.log(Level.WARNING, "Wrong game in " + f, ex);
    78                 }
    79             }
    80         }
    81     }
    82 
    83     @POST
    84     @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
    85     public GameId createGame(
    86         @QueryParam("loginID") String id,
    87         @QueryParam("white") String white,
    88         @QueryParam("black") String black
    89     ) throws IOException {
    90         String logUser = quoridor.isLoggedIn(id);
    91         if (logUser == null) {
    92             throw new WebApplicationException(Status.UNAUTHORIZED);
    93         }
    94         if (white == null) {
    95             throw new WebApplicationException(Status.PRECONDITION_FAILED);
    96         }
    97         if (black == null) {
    98             throw new WebApplicationException(Status.PRECONDITION_FAILED);
    99         }
   100         if (!logUser.equals(white) && !logUser.equals(black)) {
   101             throw new WebApplicationException(Status.PRECONDITION_FAILED);
   102         }
   103 
   104         Game g = new Game(white, black);
   105         storeGame(g);
   106         games.add(g);
   107         return g.getId();
   108     }
   109 
   110     @GET
   111     @Path("{id}")
   112     @Produces(MediaType.TEXT_PLAIN)
   113     public String getBoard(
   114         @PathParam("id") String id,
   115         @QueryParam("move") @DefaultValue("-1") int move
   116     ) {
   117         Game g = findGame(id, move);
   118         if (g == null) {
   119             throw new IllegalArgumentException("Unknown game " + id);
   120         }
   121         return g.getBoard().toString();
   122     }
   123 
   124     public Game getBoardInfo(
   125         @QueryParam("loginID") @DefaultValue("") String loginId,
   126         @PathParam("id") String id,
   127         @QueryParam("move") @DefaultValue("-1") int move
   128     ) throws IOException {
   129         Game g = findGame(id, move);
   130         if (canSee(g.getId(), loginId)) {
   131             return g;
   132         }
   133         throw new WebApplicationException(Status.UNAUTHORIZED);
   134     }
   135 
   136     @GET
   137     @Path("{id}")
   138     @Produces({ "application/x-javascript", MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
   139     public JSONWithPadding getBoardInfo(
   140         @QueryParam("callback") String callback,
   141         @QueryParam("loginID") @DefaultValue("") String loginId,
   142         @PathParam("id") String id,
   143         @QueryParam("move") @DefaultValue("-1") int move
   144     ) throws IOException {
   145         return new JSONWithPadding(getBoardInfo(loginId, id, move), callback);
   146     }
   147 
   148 
   149     private boolean canSee(GameId id, String loginId) throws IOException {
   150         if (!id.isFinished()) {
   151             return true;
   152         }
   153         String logUser = quoridor.isLoggedIn(loginId);
   154         if (logUser == null) {
   155             return false;
   156         }
   157         if (logUser.equals(id.getWhite())) {
   158             return true;
   159         }
   160         if (logUser.equals(id.getBlack())) {
   161             return true;
   162         }
   163         User info = quoridor.getUsers().getUserInfo(loginId, logUser);
   164         if (info != null && info.hasPermission("games")) {
   165             return true;
   166         }
   167         return false;
   168     }
   169 
   170     @PUT
   171     @Path("{id}")
   172     @Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
   173     public GameId applyMove(
   174         @QueryParam("loginID") String loginId,
   175         @PathParam("id") String id,
   176         @QueryParam("player") String player,
   177         @QueryParam("move") String move,
   178         @QueryParam("comment") String comment
   179     ) throws IllegalPositionException, IOException {
   180         String logUser = quoridor.isLoggedIn(loginId);
   181         if (logUser == null) {
   182             throw new WebApplicationException(Status.UNAUTHORIZED);
   183         }
   184         if (!logUser.equals(player)) {
   185             User info = quoridor.getUsers().getUserInfo(loginId, logUser);
   186             if (info == null || !info.hasPermission("resign")) {
   187                 throw new WebApplicationException(Status.UNAUTHORIZED);
   188             }
   189         }
   190         if (comment == null && move == null) {
   191             throw new WebApplicationException(Status.BAD_REQUEST);
   192         }
   193 
   194         Game g = findGame(id);
   195         if (g == null) {
   196             throw new IllegalArgumentException("Unknown game " + id);
   197         }
   198         if (move != null) {
   199             Move m = Move.valueOf(move);
   200             g.apply(player, m, new Date());
   201         }
   202         if (comment != null) {
   203             g.comment(player, comment, new Date());
   204         }
   205         try {
   206             storeGame(g);
   207         } catch (IOException ex) {
   208             LOG.log(Level.WARNING, "Cannot store game " + id, ex);
   209         }
   210         return g.getId();
   211     }
   212 
   213     @GET
   214     @Produces({ "application/x-javascript", MediaType.APPLICATION_JSON, MediaType.TEXT_XML })
   215     public JSONWithPadding listGames(
   216         @QueryParam("callback") String callback,
   217         @DefaultValue("") @QueryParam("loginID") String loginId,
   218         @DefaultValue("") @QueryParam("status") String status
   219     ) throws IOException {
   220         List<GameId> arr = new ArrayList<GameId>(games.size());
   221         for (Game g : games) {
   222             if (!canSee(g.getId(), loginId)) {
   223                 continue;
   224             }
   225             if (status.length() == 0 || g.getId().getStatus().toString().equals(status)) {
   226                 arr.add(g.getId());
   227             }
   228         }
   229         Collections.sort(arr, GameId.NEWEST_FIRST);
   230         return new JSONWithPadding(new GenericEntity<List<GameId>>(arr) {}, callback);
   231     }
   232 
   233     public List<Game> getGames() {
   234         return games;
   235     }
   236 
   237     private Game findGame(String id) {
   238         for (Game g : games) {
   239             if (g.getId().getId().equals(id)) {
   240                 return g;
   241             }
   242         }
   243         return null;
   244     }
   245     private Game findGame(String id, int move) {
   246         Game g = findGame(id);
   247         if (g == null) {
   248             throw new IllegalArgumentException("Unknown game " + id);
   249         }
   250         try {
   251             return move == -1 ? g : g.snapshot(move);
   252         } catch (IllegalPositionException ex) {
   253             Logger.getLogger(Games.class.getName()).log(Level.SEVERE, null, ex);
   254             return null;
   255         }
   256     }
   257 
   258     private static final Pattern saidWho = Pattern.compile("# *([^ ]*) *@(.*):$");
   259 
   260     private Game readGame(File f) throws IOException {
   261         InputStream is = new FileInputStream(f);
   262         BufferedReader r = new BufferedReader(new InputStreamReader(is, "UTF-8"));
   263         String white = null;
   264         String black = null;
   265         Game g = null;
   266         String who = null;
   267         String when = null;
   268         for (;;) {
   269             String line = r.readLine();
   270             if (line == null) {
   271                 line = "finish";
   272             }
   273             line = line.trim();
   274             if (line.length() == 0) {
   275                 continue;
   276             }
   277             if (line.startsWith("# white: ")) {
   278                 white = line.substring(9);
   279                 continue;
   280             }
   281             if (line.startsWith("# black: ")) {
   282                 black = line.substring(9);
   283                 continue;
   284             }
   285             if (line.startsWith("#")) {
   286                 Matcher m =saidWho.matcher(line);
   287                 if (m.matches()) {
   288                     who = m.group(1);
   289                     when = m.group(2);
   290                     continue;
   291                 }
   292                 if (g == null) {
   293                     continue;
   294                 }
   295                 if (line.startsWith("# ")) {
   296                     line = line.substring(2);
   297                 } else {
   298                     line = line.substring(1);
   299                 }
   300                 Date d = new Date();
   301                 try {
   302                     if (when != null) {
   303                         d = new Date(Date.parse(when));
   304                     }
   305                 } catch (IllegalArgumentException ex) {
   306                     LOG.warning("Unparseable date " + when + " in " + f);
   307                 }
   308                 g.comment(who, line, d);
   309                 who = null;
   310                 when = null;
   311                 continue;
   312             }
   313             if (white == null || black == null) {
   314                 throw new IOException("Missing white and black identification in " + f);
   315             }
   316             if (g == null) {
   317                 GameId id = new GameId(f.getName(), white, black, new Date(f.lastModified()), new Date(f.lastModified()), GameStatus.whiteMove, 0, false);
   318                 g = new Game(id);
   319             }
   320             int hash = line.indexOf('#');
   321             if (hash >= 0) {
   322                 line = line.substring(0, hash);
   323             }
   324             if (line.equals("finish")) {
   325                 break;
   326             }
   327             String[] moves = line.split(" ");
   328             if (moves.length == 0) {
   329                 continue;
   330             }
   331             if (moves.length > 2) {
   332                 throw new IOException("Too much moves on line: " + line);
   333             }
   334             try {
   335                 if (!"...".equals(moves[0])) {
   336                     g.apply(white, Move.valueOf(moves[0]), null);
   337                 }
   338                 if (moves.length == 2) {
   339                     g.apply(black, Move.valueOf(moves[1]), null);
   340                 }
   341             } catch (IllegalPositionException ex) {
   342                 throw new IOException("Wrong move: " + ex.getMessage());
   343             }
   344         }
   345         if (g == null) {
   346             throw new IOException("No moves in " + f);
   347         }
   348         return g;
   349     }
   350 
   351     private void storeGame(Game g) throws IOException {
   352         dir.mkdirs();
   353         File f = new File(dir, g.getId().getId());
   354         storeGame(g, f);
   355     }
   356 
   357     final void storeGame(Game g, File f) throws IOException {
   358         FileOutputStream os = new FileOutputStream(f);
   359         Writer w = new BufferedWriter(new OutputStreamWriter(os, "UTF-8"));
   360         PrintWriter pw = new PrintWriter(w);
   361         pw.println("# white: " + g.getId().getWhite());
   362         pw.println("# black: " + g.getId().getBlack());
   363         pw.println("# status: " + g.getId().getStatus());
   364         int cnt = 0;
   365         boolean separate = true;
   366         for (CommentedMove m : g.getMoves()) {
   367             if (!separate && cnt % 2 == 1) {
   368                 pw.print("... ");
   369             }
   370             separate = true;
   371             pw.print(m.getMove().toString());
   372             List<Note> notes = m.getComments();
   373             if (notes != null) {
   374                 separate = false;
   375                 pw.println();
   376                 for (Note n : notes) {
   377                     pw.print ("# ");
   378                     pw.print(n.getWho());
   379                     pw.print("@");
   380                     pw.print(n.getWhen());
   381                     pw.println(":");
   382                     for (String l : n.getComment().split("\n")) {
   383                         pw.print("# ");
   384                         pw.println(l);
   385                     }
   386                 }
   387             }
   388 
   389             cnt++;
   390 
   391             if (separate) {
   392                 if (cnt % 2 == 0) {
   393                     pw.println();
   394                 } else {
   395                     pw.print(' ');
   396                 }
   397             }
   398         }
   399         pw.println();
   400         pw.println();
   401         pw.flush();
   402         pw.close();
   403         w.close();
   404     }
   405 }