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