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