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