846e135312de809439345bf87d9445af4935772d
[jSite2.git] / src / net / pterodactylus / util / fcp / FcpConnection.java
1 /*
2  * jSite2 - FpcConnection.java -
3  * Copyright © 2008 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.util.fcp;
21
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.io.OutputStream;
25 import java.net.InetAddress;
26 import java.net.Socket;
27 import java.net.UnknownHostException;
28 import java.util.ArrayList;
29 import java.util.List;
30
31 import net.pterodactylus.util.fcp.message.CloseConnectionDuplicateClientName;
32 import net.pterodactylus.util.fcp.message.NodeHello;
33 import net.pterodactylus.util.fcp.message.SSKKeypair;
34 import net.pterodactylus.util.io.Closer;
35
36 /**
37  * An FCP connection to a Freenet node.
38  * 
39  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
40  * @version $Id$
41  */
42 public class FcpConnection {
43
44         /** The default port for FCP v2. */
45         public static final int DEFAULT_PORT = 9481;
46
47         /** The list of FCP listeners. */
48         private final List<FcpListener> fcpListeners = new ArrayList<FcpListener>();
49
50         /** The address of the node. */
51         private final InetAddress address;
52
53         /** The port number of the node’s FCP port. */
54         private final int port;
55
56         /** The remote socket. */
57         private Socket remoteSocket;
58
59         /** The input stream from the node. */
60         private InputStream remoteInputStream;
61
62         /** The output stream to the node. */
63         private OutputStream remoteOutputStream;
64
65         /** The connection handler. */
66         private FcpConnectionHandler connectionHandler;
67
68         /**
69          * Creates a new FCP connection to the Freenet node running on the given
70          * host, listening on the default port.
71          * 
72          * @param host
73          *            The hostname of the Freenet node
74          * @throws UnknownHostException
75          *             if <code>host</code> can not be resolved
76          */
77         public FcpConnection(String host) throws UnknownHostException {
78                 this(host, DEFAULT_PORT);
79         }
80
81         /**
82          * Creates a new FCP connection to the Freenet node running on the given
83          * host, listening on the given port.
84          * 
85          * @param host
86          *            The hostname of the Freenet node
87          * @param port
88          *            The port number of the node’s FCP port
89          * @throws UnknownHostException
90          *             if <code>host</code> can not be resolved
91          */
92         public FcpConnection(String host, int port) throws UnknownHostException {
93                 this(InetAddress.getByName(host), port);
94         }
95
96         /**
97          * Creates a new FCP connection to the Freenet node running at the given
98          * address, listening on the default port.
99          * 
100          * @param address
101          *            The address of the Freenet node
102          */
103         public FcpConnection(InetAddress address) {
104                 this(address, DEFAULT_PORT);
105         }
106
107         /**
108          * Creates a new FCP connection to the Freenet node running at the given
109          * address, listening on the given port.
110          * 
111          * @param address
112          *            The address of the Freenet node
113          * @param port
114          *            The port number of the node’s FCP port
115          */
116         public FcpConnection(InetAddress address, int port) {
117                 this.address = address;
118                 this.port = port;
119         }
120
121         //
122         // LISTENER MANAGEMENT
123         //
124
125         /**
126          * Adds the given listener to the list of listeners.
127          * 
128          * @param fcpListener
129          *            The listener to add
130          */
131         public void addFcpListener(FcpListener fcpListener) {
132                 fcpListeners.add(fcpListener);
133         }
134
135         /**
136          * Removes the given listener from the list of listeners.
137          * 
138          * @param fcpListener
139          *            The listener to remove
140          */
141         public void removeFcpListener(FcpListener fcpListener) {
142                 fcpListeners.remove(fcpListener);
143         }
144
145         /**
146          * Notifies listeners that a “NodeHello” message was received.
147          * 
148          * @see FcpListener#receivedNodeHello(FcpConnection, NodeHello)
149          * @param nodeHello
150          *            The “NodeHello” message
151          */
152         private void fireReceivedNodeHello(NodeHello nodeHello) {
153                 for (FcpListener fcpListener: fcpListeners) {
154                         fcpListener.receivedNodeHello(this, nodeHello);
155                 }
156         }
157
158         /**
159          * Notifies listeners that a “CloseConnectionDuplicateClientName” message
160          * was received.
161          * 
162          * @see FcpListener#receivedCloseConnectionDuplicateClientName(FcpConnection,
163          *      CloseConnectionDuplicateClientName)
164          * @param closeConnectionDuplicateClientName
165          *            The “CloseConnectionDuplicateClientName” message
166          */
167         private void fireReceivedCloseConnectionDuplicateClientName(CloseConnectionDuplicateClientName closeConnectionDuplicateClientName) {
168                 for (FcpListener fcpListener: fcpListeners) {
169                         fcpListener.receivedCloseConnectionDuplicateClientName(this, closeConnectionDuplicateClientName);
170                 }
171         }
172
173         /**
174          * Notifies listeners that a “SSKKeypair” message was received.
175          * 
176          * @see FcpListener#receivedSSKKeypair(FcpConnection, SSKKeypair)
177          * @param sskKeypair
178          *            The “SSKKeypair” message
179          */
180         private void fireReceivedSSKKeypair(SSKKeypair sskKeypair) {
181                 for (FcpListener fcpListener: fcpListeners) {
182                         fcpListener.receivedSSKKeypair(this, sskKeypair);
183                 }
184         }
185
186         /**
187          * Notifies all registered listeners that a message has been received.
188          * 
189          * @see FcpListener#receivedMessage(FcpConnection, FcpMessage)
190          * @param fcpMessage
191          *            The message that was received
192          */
193         private void fireMessageReceived(FcpMessage fcpMessage) {
194                 for (FcpListener fcpListener: fcpListeners) {
195                         fcpListener.receivedMessage(this, fcpMessage);
196                 }
197         }
198
199         //
200         // ACTIONS
201         //
202
203         /**
204          * Connects to the node.
205          * 
206          * @throws IOException
207          *             if an I/O error occurs
208          * @throws IllegalStateException
209          *             if there is already a connection to the node
210          */
211         public synchronized void connect() throws IOException, IllegalStateException {
212                 if (connectionHandler != null) {
213                         throw new IllegalStateException("already connected, disconnect first");
214                 }
215                 remoteSocket = new Socket(address, port);
216                 remoteInputStream = remoteSocket.getInputStream();
217                 remoteOutputStream = remoteSocket.getOutputStream();
218                 new Thread(connectionHandler = new FcpConnectionHandler(this, remoteInputStream)).start();
219         }
220
221         /**
222          * Disconnects from the node. If there is no connection to the node, this
223          * method does nothing.
224          */
225         public synchronized void disconnect() {
226                 if (connectionHandler == null) {
227                         return;
228                 }
229                 Closer.close(remoteSocket);
230                 connectionHandler.stop();
231                 connectionHandler = null;
232         }
233
234         /**
235          * Sends the given FCP message.
236          * 
237          * @param fcpMessage
238          *            The FCP message to send
239          * @throws IOException
240          *             if an I/O error occurs
241          */
242         public synchronized void sendMessage(FcpMessage fcpMessage) throws IOException {
243                 System.out.println("sending message: " + fcpMessage.getName());
244                 fcpMessage.write(remoteOutputStream);
245         }
246
247         //
248         // PACKAGE-PRIVATE METHODS
249         //
250
251         /**
252          * Handles the given message, notifying listeners. This message should only
253          * be called by {@link FcpConnectionHandler}.
254          * 
255          * @param fcpMessage
256          *            The received message
257          */
258         void handleMessage(FcpMessage fcpMessage) {
259                 String messageName = fcpMessage.getName();
260                 if ("SSKKeypair".equals(messageName)) {
261                         fireReceivedSSKKeypair(new SSKKeypair(fcpMessage));
262                 } else if ("NodeHello".equals(messageName)) {
263                         fireReceivedNodeHello(new NodeHello(fcpMessage));
264                 } else if ("CloseConnectionDuplicateClientName".equals(messageName)) {
265                         fireReceivedCloseConnectionDuplicateClientName(new CloseConnectionDuplicateClientName(fcpMessage));
266                 } else {
267                         fireMessageReceived(fcpMessage);
268                 }
269         }
270
271 }