/** * Copyright (c) 2012, Andy Janata * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted * provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this list of conditions * and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, this list of * conditions and the following disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package net.socialgamer.cah.data; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.PriorityBlockingQueue; /** * A user connected to the server. * * @author Andy Janata (ajanata@socialgamer.net) */ public class User { private final String nickname; private final PriorityBlockingQueue<QueuedMessage> queuedMessages; private final Object queuedMessageSynchronization = new Object(); private long lastHeardFrom = 0; private long lastUserAction = 0; private Game currentGame; private final String hostName; private final boolean isAdmin; private final List<Long> lastMessageTimes = Collections.synchronizedList(new LinkedList<Long>()); /** * Reset when this user object is no longer valid, most likely because it pinged out. */ private boolean valid = true; /** * Create a new user. * * @param nickname * The user's nickname. * @param hostName * The user's Internet hostname (which will likely just be their IP address). * @param isAdmin * Whether this user is an admin. */ public User(final String nickname, final String hostName, final boolean isAdmin) { this.nickname = nickname; this.hostName = hostName; this.isAdmin = isAdmin; queuedMessages = new PriorityBlockingQueue<QueuedMessage>(); } /** * Enqueue a new message to be delivered to the user. * * @param message * Message to enqueue. */ public void enqueueMessage(final QueuedMessage message) { synchronized (queuedMessageSynchronization) { queuedMessages.add(message); queuedMessageSynchronization.notifyAll(); } } /** * @return True if the user has any messages queued to be delivered. */ public boolean hasQueuedMessages() { return !queuedMessages.isEmpty(); } /** * Wait for a new message to be queued. * * @see java.lang.Object#wait(long timeout) * @param timeout * Maximum time to wait in milliseconds. * @throws InterruptedException */ public void waitForNewMessageNotification(final long timeout) throws InterruptedException { if (timeout > 0) { synchronized (queuedMessageSynchronization) { queuedMessageSynchronization.wait(timeout); } } } /** * This method blocks if there are no messages to return, or perhaps if the queue is being * modified by another thread. * * @return The next message in the queue, or null if interrupted. */ public QueuedMessage getNextQueuedMessage() { try { return queuedMessages.take(); } catch (final InterruptedException ie) { return null; } } /** * @param maxElements * Maximum number of messages to return. * @return The next {@code maxElements} messages queued for this user. */ public Collection<QueuedMessage> getNextQueuedMessages(final int maxElements) { final ArrayList<QueuedMessage> c = new ArrayList<QueuedMessage>(maxElements); synchronized (queuedMessageSynchronization) { queuedMessages.drainTo(c, maxElements); } c.trimToSize(); return c; } public boolean isAdmin() { return isAdmin; } /** * @return The user's nickname. */ public String getNickname() { return nickname; } /** * @return The user's Internet hostname, or IP address. */ public String getHostName() { return hostName; } @Override public String toString() { return getNickname(); } /** * Update the timestamp that we have last heard from this user to the current time. */ public void contactedServer() { lastHeardFrom = System.nanoTime(); } /** * @return The time the user was last heard from, in nanoseconds. */ public long getLastHeardFrom() { return lastHeardFrom; } public void userDidSomething() { lastUserAction = System.nanoTime(); } public long getLastUserAction() { return lastUserAction; } /** * @return False when this user object is no longer valid, probably because it pinged out. */ public boolean isValid() { return valid; } /** * Mark this user as no longer valid, probably because they pinged out. */ public void noLongerVaild() { if (currentGame != null) { currentGame.removePlayer(this); } valid = false; } /** * @return The current game in which this user is participating. */ public Game getGame() { return currentGame; } /** * Marks a given game as this user's active game. * * This should only be called from Game itself. * * @param game * Game in which this user is playing. * @throws IllegalStateException * Thrown if this user is already in another game. */ void joinGame(final Game game) throws IllegalStateException { if (currentGame != null) { throw new IllegalStateException("User is already in a game."); } currentGame = game; } /** * Marks the user as no longer participating in a game. * * This should only be called from Game itself. * * @param game * Game from which to remove the user. */ void leaveGame(final Game game) { if (currentGame == game) { currentGame = null; } } public List<Long> getLastMessageTimes() { return lastMessageTimes; } }