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