Add fluent method to set a field
[jFCPlib.git] / src / main / java / net / pterodactylus / fcp / FcpMessage.java
1 /*
2  * jFCPlib - FcpMessage.java - Copyright © 2008 David Roden
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 package net.pterodactylus.fcp;
20
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.io.OutputStream;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.Map;
28 import java.util.Map.Entry;
29
30 /**
31  * An FCP message. FCP messages consist of a name, an arbitrary amount of
32  * “fields” (i.e. key-value pairs), a message end marker, and optional payload
33  * data that follows the marker.
34  *
35  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
36  */
37 public class FcpMessage implements Iterable<String> {
38
39         /** Constant for the linefeed. */
40         private static final String LINEFEED = "\r\n";
41
42         /** The name of the message. */
43         private final String name;
44
45         /** The fields of the message. */
46         private final Map<String, String> fields = new HashMap<String, String>();
47
48         /** The optional payload input stream. */
49         private InputStream payloadInputStream;
50
51         /**
52          * Creates a new FCP message with the given name.
53          *
54          * @param name
55          *            The name of the FCP message
56          */
57         public FcpMessage(String name) {
58                 this(name, null);
59         }
60
61         /**
62          * Creates a new FCP message with the given name and the given payload
63          * input stream. The payload input stream is not read until the message is
64          * sent to the node using {@link FcpConnection#sendMessage(FcpMessage)}.
65          *
66          * @param name
67          *            The name of the message
68          * @param payloadInputStream
69          *            The payload of the message
70          */
71         public FcpMessage(String name, InputStream payloadInputStream) {
72                 this.name = name;
73                 this.payloadInputStream = payloadInputStream;
74         }
75
76         /**
77          * Returns the name of the message.
78          *
79          * @return The name of the message
80          */
81         public String getName() {
82                 return name;
83         }
84
85         /**
86          * Checks whether this message has a field with the given name.
87          *
88          * @param field
89          *            The name of the field to check for
90          * @return <code>true</code> if the message has a field with the given
91          *         name, <code>false</code> otherwise
92          */
93         public boolean hasField(String field) {
94                 return fields.containsKey(field);
95         }
96
97         /**
98          * Sets the field with the given name to the given value. If the field
99          * already exists in this message it is overwritten.
100          *
101          * @param field
102          *            The name of the field
103          * @param value
104          *            The value of the field
105          */
106         public void setField(String field, String value) {
107                 if ((field == null) || (value == null)) {
108                         throw new NullPointerException(((field == null) ? "field " : "value ") + "must not be null");
109                 }
110                 fields.put(field, value);
111         }
112
113         public FcpMessage put(String field, String value) {
114                 setField(field, value);
115                 return this;
116         }
117
118         /**
119          * Returns the value of the given field.
120          *
121          * @param field
122          *            The name of the field
123          * @return The value of the field, or <code>null</code> if there is no such
124          *         field
125          */
126         public String getField(String field) {
127                 return fields.get(field);
128         }
129
130         /**
131          * Returns all fields of this message.
132          *
133          * @return All fields of this message
134          */
135         public Map<String, String> getFields() {
136                 return Collections.unmodifiableMap(fields);
137         }
138
139         /**
140          * {@inheritDoc}
141          */
142         @Override
143         public Iterator<String> iterator() {
144                 return fields.keySet().iterator();
145         }
146
147         /**
148          * Sets the payload input stream of the message.
149          *
150          * @param payloadInputStream
151          *            The payload input stream
152          */
153         public void setPayloadInputStream(InputStream payloadInputStream) {
154                 this.payloadInputStream = payloadInputStream;
155         }
156
157         /**
158          * Writes this message to the given output stream. If the message has a
159          * payload (i.e. {@link #payloadInputStream} is not <code>null</code>) the
160          * payload is written to the given output stream after the message as well.
161          * That means that this method can only be called once because on the
162          * second invocation the payload input stream could not be read (again).
163          *
164          * @param outputStream
165          *            The output stream to write the message to
166          * @throws IOException
167          *             if an I/O error occurs
168          */
169         public void write(OutputStream outputStream) throws IOException {
170                 writeLine(outputStream, name);
171                 for (Entry<String, String> fieldEntry : fields.entrySet()) {
172                         writeLine(outputStream, fieldEntry.getKey() + "=" + fieldEntry.getValue());
173                 }
174                 writeLine(outputStream, "EndMessage");
175                 outputStream.flush();
176                 if (payloadInputStream != null) {
177                         FcpUtils.copy(payloadInputStream, outputStream);
178                         outputStream.flush();
179                 }
180         }
181
182         //
183         // PRIVATE METHODS
184         //
185
186         /**
187          * Writes the given line (followed by {@link #LINEFEED} to the given output
188          * stream, using UTF-8 as encoding.
189          *
190          * @param outputStream
191          *            The output stream to write to
192          * @param line
193          *            The line to write
194          * @throws IOException
195          *             if an I/O error occurs
196          */
197         private void writeLine(OutputStream outputStream, String line) throws IOException {
198                 outputStream.write((line + LINEFEED).getBytes("UTF-8"));
199         }
200
201 }