Add additional logging to find out why getMessage() returns null.
[jSite.git] / src / main / java / de / todesbaum / util / freenet / fcp2 / Client.java
1 /*
2  * jSite - Client.java - Copyright © 2006–2012 David Roden
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
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; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 package de.todesbaum.util.freenet.fcp2;
20
21 import static java.util.logging.Logger.getLogger;
22
23 import java.io.IOException;
24 import java.util.ArrayList;
25 import java.util.List;
26 import java.util.logging.Logger;
27
28 import net.pterodactylus.util.io.StreamCopier.ProgressListener;
29
30 /**
31  * A Client executes {@link Command}s over a {@link Connection} to a
32  * {@link Node} and delivers resulting {@link Message}s.
33  *
34  * @author David Roden <droden@gmail.com>
35  * @version $Id$
36  */
37 public class Client implements ConnectionListener {
38
39         private static final Logger logger = getLogger(Client.class.getName());
40
41         /** The connection this client operates on. */
42         private final Connection connection;
43
44         /** The identifiers the client filters messages for. */
45         private List<String> identifiers = new ArrayList<String>();
46
47         /** The queued messages. */
48         private final List<Message> messageQueue = new ArrayList<Message>();
49
50         /** Whether the client was disconnected. */
51         private boolean disconnected = false;
52
53         /** Whether to catch all messages from the connection. */
54         private boolean catchAll = false;
55
56         /**
57          * Creates a new client that operates on the specified connection.
58          *
59          * @param connection
60          *            The connection to operate on
61          */
62         public Client(Connection connection) {
63                 this.connection = connection;
64                 connection.addConnectionListener(this);
65         }
66
67         /**
68          * Creates a new client that operates on the specified connection and
69          * immediately executes the specified command.
70          *
71          * @param connection
72          *            The connection to operate on
73          * @param command
74          *            The command to execute
75          * @throws IOException
76          *             if an I/O error occurs
77          * @see #execute(Command)
78          */
79         public Client(Connection connection, Command command) throws IOException {
80                 this(connection);
81                 execute(command);
82         }
83
84         /**
85          * Returns whether this client catches all messages going over the
86          * connection.
87          *
88          * @return <code>true</code> if the client catches all messages,
89          *         <code>false</code> otherwise
90          */
91         public boolean isCatchAll() {
92                 return catchAll;
93         }
94
95         /**
96          * Sets whether this client catches all messages going over the connection.
97          *
98          * @param catchAll
99          *            <code>true</code> if the client should catch all messages,
100          *            <code>false</code> otherwise
101          */
102         public void setCatchAll(boolean catchAll) {
103                 this.catchAll = catchAll;
104         }
105
106         /**
107          * Executes the specified command. This will also clear the queue of
108          * messages, discarding all messages that resulted from the previous command
109          * and have not yet been read.
110          *
111          * @param command
112          *            The command to execute
113          * @throws IOException
114          *             if an I/O error occurs
115          * @see #execute(Command, boolean)
116          */
117         public void execute(Command command) throws IOException {
118                 execute(command, true);
119         }
120
121         /**
122          * Executes the specified command. This will also clear the queue of
123          * messages, discarding all messages that resulted from the previous
124          * command and have not yet been read.
125          *
126          * @param command
127          *            The command to execute
128          * @param progressListener
129          *            The progress listener for payload transfers
130          * @throws IOException
131          *             if an I/O error occurs
132          * @see #execute(Command, boolean)
133          */
134         public void execute(Command command, ProgressListener progressListener) throws IOException {
135                 execute(command, true, progressListener);
136         }
137
138         /**
139          * Executes the specified command and optionally clears the list of
140          * identifiers this clients listens to before starting the command.
141          *
142          * @param command
143          *            The command to execute
144          * @param removeExistingIdentifiers
145          *            If <code>true</code>, the list of identifiers that this
146          *            clients listens to is cleared
147          * @throws IOException
148          *             if an I/O error occurs
149          */
150         public void execute(Command command, boolean removeExistingIdentifiers) throws IOException {
151                 execute(command, removeExistingIdentifiers, null);
152         }
153
154         /**
155          * Executes the specified command and optionally clears the list of
156          * identifiers this clients listens to before starting the command.
157          *
158          * @param command
159          *            The command to execute
160          * @param removeExistingIdentifiers
161          *            If <code>true</code>, the list of identifiers that this
162          *            clients listens to is cleared
163          * @param progressListener
164          *            The progress listener for payload transfers
165          * @throws IOException
166          *             if an I/O error occurs
167          */
168         public void execute(Command command, boolean removeExistingIdentifiers, ProgressListener progressListener) throws IOException {
169                 synchronized (messageQueue) {
170                         messageQueue.clear();
171                         if (removeExistingIdentifiers) {
172                                 identifiers.clear();
173                         }
174                         identifiers.add(command.getIdentifier());
175                 }
176                 connection.execute(command, progressListener);
177         }
178
179         /**
180          * Returns the next message, waiting endlessly for it, if need be. If you
181          * are not sure whether a message will arrive, better use
182          * {@link #readMessage(long)} to only wait for a specific time.
183          *
184          * @return The next message that resulted from the execution of the last
185          *         command
186          * @see #readMessage(long)
187          * @see #execute(Command)
188          */
189         public Message readMessage() {
190                 return readMessage(0);
191         }
192
193         /**
194          * Returns the next message. If the message queue is currently empty, at
195          * least <code>maxWaitTime</code> milliseconds will be waited for a
196          * message to arrive.
197          *
198          * @param maxWaitTime
199          *            The minimum time to wait for a message, in milliseconds
200          * @return The message, or <code>null</code> if no message arrived in time
201          *         or the client is currently disconnected
202          * @see #isDisconnected()
203          * @see Object#wait(long)
204          */
205         public Message readMessage(long maxWaitTime) {
206                 synchronized (messageQueue) {
207                         if (disconnected) {
208                                 logger.fine("Disconnected.");
209                                 return null;
210                         }
211                         if (messageQueue.size() == 0) {
212                                 try {
213                                         messageQueue.wait(maxWaitTime);
214                                 } catch (InterruptedException ie1) {
215                                         logger.fine("Interrupted.");
216                                 }
217                         }
218                         if (messageQueue.size() > 0) {
219                                 Message message = messageQueue.remove(0);
220                                 logger.fine("Got a message: " + message);
221                                 return message;
222                         }
223                 }
224                 logger.fine("Got no message.");
225                 return null;
226         }
227
228         /**
229          * Returns whether the client is currently disconnected.
230          *
231          * @return <code>true</code> if the client is disconnected,
232          *         <code>false</code> otherwise
233          */
234         public boolean isDisconnected() {
235                 synchronized (messageQueue) {
236                         return disconnected;
237                 }
238         }
239
240         /**
241          * {@inheritDoc}
242          */
243         public void messageReceived(Connection connection, Message message) {
244                 synchronized (messageQueue) {
245                         if (catchAll || (message.getIdentifier().length() == 0) || identifiers.contains(message.getIdentifier())) {
246                                 messageQueue.add(message);
247                                 messageQueue.notify();
248                         }
249                 }
250         }
251
252         /**
253          * {@inheritDoc}
254          */
255         public void connectionTerminated(Connection connection) {
256                 synchronized (messageQueue) {
257                         disconnected = true;
258                         messageQueue.notify();
259                 }
260         }
261
262 }