Add try-finally block to ensure removal of FCP listener.
[jFCPlib.git] / src / net / pterodactylus / fcp / highlevel / FcpClient.java
1 /*
2  * jFCPlib - FcpClient.java -
3  * Copyright © 2009 David Roden
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 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.fcp.highlevel;
21
22 import java.io.IOException;
23 import java.net.InetAddress;
24 import java.net.UnknownHostException;
25 import java.util.concurrent.CountDownLatch;
26
27 import net.pterodactylus.fcp.ClientHello;
28 import net.pterodactylus.fcp.CloseConnectionDuplicateClientName;
29 import net.pterodactylus.fcp.FcpAdapter;
30 import net.pterodactylus.fcp.FcpConnection;
31 import net.pterodactylus.fcp.FcpListener;
32 import net.pterodactylus.fcp.NodeHello;
33 import net.pterodactylus.fcp.ProtocolError;
34
35 /**
36  * High-level FCP client that hides the details of the underlying FCP
37  * implementation.
38  *
39  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
40  */
41 public class FcpClient {
42
43         /** Object used for synchronization. */
44         private final Object syncObject = new Object();
45
46         /** The name of this client. */
47         private final String name;
48
49         /** The underlying FCP connection. */
50         private final FcpConnection fcpConnection;
51
52         /**
53          * Creates an FCP client with the given name.
54          *
55          * @param name
56          *            The name of the FCP client
57          * @throws UnknownHostException
58          *             if the hostname “localhost” is unknown
59          */
60         public FcpClient(String name) throws UnknownHostException {
61                 this(name, "localhost");
62         }
63
64         /**
65          * Creates an FCP client.
66          *
67          * @param name
68          *            The name of the FCP client
69          * @param hostname
70          *            The hostname of the Freenet node
71          * @throws UnknownHostException
72          *             if the given hostname can not be resolved
73          */
74         public FcpClient(String name, String hostname) throws UnknownHostException {
75                 this(name, hostname, FcpConnection.DEFAULT_PORT);
76         }
77
78         /**
79          * Creates an FCP client.
80          *
81          * @param name
82          *            The name of the FCP client
83          * @param hostname
84          *            The hostname of the Freenet node
85          * @param port
86          *            The Freenet node’s FCP port
87          * @throws UnknownHostException
88          *             if the given hostname can not be resolved
89          */
90         public FcpClient(String name, String hostname, int port) throws UnknownHostException {
91                 this(name, InetAddress.getByName(hostname), port);
92         }
93
94         /**
95          * Creates an FCP client.
96          *
97          * @param name
98          *            The name of the FCP client
99          * @param host
100          *            The host address of the Freenet node
101          */
102         public FcpClient(String name, InetAddress host) {
103                 this(name, host, FcpConnection.DEFAULT_PORT);
104         }
105
106         /**
107          * Creates an FCP client.
108          *
109          * @param name
110          *            The name of the FCP client
111          * @param host
112          *            The host address of the Freenet node
113          * @param port
114          *            The Freenet node’s FCP port
115          */
116         public FcpClient(String name, InetAddress host, int port) {
117                 this.name = name;
118                 fcpConnection = new FcpConnection(host, port);
119         }
120
121         //
122         // ACTIONS
123         //
124
125         /**
126          * Connects the FCP client.
127          *
128          * @throws IOException
129          *             if an I/O error occurs
130          * @throws FcpException
131          *             if an FCP error occurs
132          */
133         public void connect() throws IOException, FcpException {
134                 ExtendedFcpAdapter fcpListener = new ExtendedFcpAdapter() {
135
136                         /**
137                          * {@inheritDoc}
138                          */
139                         @Override
140                         public void receivedNodeHello(FcpConnection fcpConnection, NodeHello nodeHello) {
141                                 completionLatch.countDown();
142                         }
143                 };
144                 fcpConnection.addFcpListener(fcpListener);
145                 try {
146                         fcpConnection.connect();
147                         ClientHello clientHello = new ClientHello(name);
148                         fcpConnection.sendMessage(clientHello);
149                         while (true) {
150                                 try {
151                                         fcpListener.complete();
152                                         break;
153                                 } catch (InterruptedException e) {
154                                         /* ignore, we’ll loop. */
155                                 }
156                         }
157                 } finally {
158                         fcpConnection.removeFcpListener(fcpListener);
159                 }
160                 if (fcpListener.getFcpException() != null) {
161                         throw fcpListener.getFcpException();
162                 }
163         }
164
165         /**
166          * Disconnects the FCP client.
167          */
168         public void disconnect() {
169                 synchronized (syncObject) {
170                         fcpConnection.close();
171                         syncObject.notifyAll();
172                 }
173         }
174
175         /**
176          * Implementation of an {@link FcpListener} that can store an
177          * {@link FcpException} and wait for the arrival of a certain command.
178          *
179          * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
180          */
181         private static class ExtendedFcpAdapter extends FcpAdapter {
182
183                 /** The count down latch used to wait for completion. */
184                 protected final CountDownLatch completionLatch = new CountDownLatch(1);
185
186                 /** The FCP exception, if any. */
187                 protected FcpException fcpException;
188
189                 /**
190                  * Creates a new extended FCP adapter.
191                  */
192                 public ExtendedFcpAdapter() {
193                         /* do nothing. */
194                 }
195
196                 /**
197                  * Returns the FCP exception that occured. If no FCP exception occured,
198                  * <code>null</code> is returned.
199                  *
200                  * @return The FCP exception that occured, or <code>null</code>
201                  */
202                 public FcpException getFcpException() {
203                         return fcpException;
204                 }
205
206                 /**
207                  * Waits for the completion of the command.
208                  *
209                  * @throws InterruptedException
210                  *             if {@link CountDownLatch#await()} is interrupted
211                  */
212                 public void complete() throws InterruptedException {
213                         completionLatch.await();
214                 }
215
216                 /**
217                  * {@inheritDoc}
218                  */
219                 @Override
220                 public void connectionClosed(FcpConnection fcpConnection, Throwable throwable) {
221                         fcpException = new FcpException("Connection closed", throwable);
222                         completionLatch.countDown();
223                 }
224
225                 /**
226                  * {@inheritDoc}
227                  */
228                 @Override
229                 public void receivedCloseConnectionDuplicateClientName(FcpConnection fcpConnection, CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
230                         fcpException = new FcpException("Connection closed, duplicate client name");
231                         completionLatch.countDown();
232                 }
233
234                 /**
235                  * {@inheritDoc}
236                  */
237                 @Override
238                 public void receivedProtocolError(FcpConnection fcpConnection, ProtocolError protocolError) {
239                         fcpException = new FcpException("Protocol error (" + protocolError.getCode() + ", " + protocolError.getCodeDescription());
240                         completionLatch.countDown();
241                 }
242
243         }
244
245 }