implement node addition and removal events
[jSite2.git] / src / net / pterodactylus / util / fcp / FcpMessage.java
1 /*
2  * jSite2 - FcpMessage.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.util.Collections;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.Map.Entry;
30
31 import net.pterodactylus.util.io.StreamCopier;
32
33 /**
34  * An FCP message. FCP messages consist of a name, an arbitrary amount of
35  * “fields” (i.e. key-value pairs), a message end marker, and optional payload
36  * data that follows the marker.
37  * 
38  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
39  * @version $Id$
40  */
41 public class FcpMessage implements Iterable<String> {
42
43         /** Constant for the linefeed. */
44         private static final String LINEFEED = "\r\n";
45
46         /** The name of the message. */
47         private final String name;
48
49         /** The fields of the message. */
50         private final Map<String, String> fields = new HashMap<String, String>();
51
52         /** The optional payload input stream. */
53         private InputStream payloadInputStream;
54
55         /**
56          * Creates a new FCP message with the given name.
57          * 
58          * @param name
59          *            The name of the FCP message
60          */
61         public FcpMessage(String name) {
62                 this(name, null);
63         }
64
65         /**
66          * Creates a new FCP message with the given name and the given payload input
67          * stream. The payload input stream is not read until the message is sent to
68          * the node using {@link FcpConnection#sendMessage(FcpMessage)}.
69          * 
70          * @param name
71          *            The name of the message
72          * @param payloadInputStream
73          *            The payload of the message
74          */
75         public FcpMessage(String name, InputStream payloadInputStream) {
76                 this.name = name;
77                 this.payloadInputStream = payloadInputStream;
78         }
79
80         /**
81          * Returns the name of the message.
82          * 
83          * @return The name of the message
84          */
85         public String getName() {
86                 return name;
87         }
88
89         /**
90          * Checks whether this message has a field with the given name.
91          * 
92          * @param field
93          *            The name of the field to check for
94          * @return <code>true</code> if the message has a field with the given
95          *         name, <code>false</code> otherwise
96          */
97         public boolean hasField(String field) {
98                 return fields.containsKey(field);
99         }
100
101         /**
102          * Sets the field with the given name to the given value. If the field
103          * already exists in this message it is overwritten.
104          * 
105          * @param field
106          *            The name of the field
107          * @param value
108          *            The value of the field
109          */
110         public void setField(String field, String value) {
111                 if ((field == null) || (value == null)) {
112                         throw new NullPointerException(((field == null) ? "field " : "value ") + "must not be null");
113                 }
114                 fields.put(field, value);
115         }
116
117         /**
118          * Returns the value of the given field.
119          * 
120          * @param field
121          *            The name of the field
122          * @return The value of the field, or <code>null</code> if there is no
123          *         such field
124          */
125         public String getField(String field) {
126                 return fields.get(field);
127         }
128
129         /**
130          * Returns all fields of this message.
131          * 
132          * @return All fields of this message
133          */
134         public Map<String, String> getFields() {
135                 return Collections.unmodifiableMap(fields);
136         }
137
138         /**
139          * {@inheritDoc}
140          */
141         public Iterator<String> iterator() {
142                 return fields.keySet().iterator();
143         }
144
145         /**
146          * Sets the payload input stream of the message.
147          * 
148          * @param payloadInputStream
149          *            The payload input stream
150          */
151         public void setPayloadInputStream(InputStream payloadInputStream) {
152                 this.payloadInputStream = payloadInputStream;
153         }
154
155         /**
156          * Writes this message to the given output stream. If the message has a
157          * payload (i.e. {@link #payloadInputStream} is not <code>null</code>)
158          * the payload is written to the given output stream after the message as
159          * well. That means that this method can only be called once because on the
160          * second invocation the payload input stream could not be read (again).
161          * 
162          * @param outputStream
163          *            The output stream to write the message to
164          * @throws IOException
165          *             if an I/O error occurs
166          */
167         public void write(OutputStream outputStream) throws IOException {
168                 writeLine(outputStream, name);
169                 for (Entry<String, String> fieldEntry: fields.entrySet()) {
170                         writeLine(outputStream, fieldEntry.getKey() + "=" + fieldEntry.getValue());
171                 }
172                 writeLine(outputStream, "EndMessage");
173                 outputStream.flush();
174                 if (payloadInputStream != null) {
175                         StreamCopier.copy(payloadInputStream, outputStream);
176                 }
177                 outputStream.flush();
178         }
179
180         //
181         // PRIVATE METHODS
182         //
183
184         /**
185          * Writes the given line (followed by {@link #LINEFEED} to the given output
186          * stream, using UTF-8 as encoding.
187          * 
188          * @param outputStream
189          *            The output stream to write to
190          * @param line
191          *            The line to write
192          * @throws IOException
193          *             if an I/O error occurs
194          */
195         private void writeLine(OutputStream outputStream, String line) throws IOException {
196                 outputStream.write((line + LINEFEED).getBytes("UTF-8"));
197         }
198
199 }