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