Performance improvement:
[jvcard.git] / src / be / nikiroo / jvcard / remote / SimpleSocket.java
1 package be.nikiroo.jvcard.remote;
2
3 import java.io.BufferedReader;
4 import java.io.Closeable;
5 import java.io.IOException;
6 import java.io.InputStreamReader;
7 import java.io.PrintWriter;
8 import java.net.InetAddress;
9 import java.net.Socket;
10 import java.util.Arrays;
11 import java.util.LinkedList;
12 import java.util.List;
13
14 /**
15 * A client or server connection, that will allow you to connect to, send and
16 * receive data to/from a jVCard remote server.
17 *
18 *
19 *
20 * @author niki
21 */
22 public class SimpleSocket {
23 /**
24 * An {@link Appendable} that can be used to send data over a
25 * {@link SimpleSocket}. You must close it to send the end of block element.
26 *
27 * @author niki
28 *
29 */
30 public class BlockAppendable implements Appendable, Closeable {
31 private SimpleSocket ss;
32
33 /**
34 * Create a new {@link BlockAppendable} for the given
35 * {@link SimpleSocket}.
36 *
37 * @param ss
38 * the {@link SimpleSocket}
39 */
40 public BlockAppendable(SimpleSocket ss) {
41 this.ss = ss;
42 }
43
44 @Override
45 public Appendable append(CharSequence csq) throws IOException {
46 ss.send(csq);
47 return this;
48 }
49
50 @Override
51 public Appendable append(char c) throws IOException {
52 ss.send("" + c);
53 return this;
54 }
55
56 @Override
57 public Appendable append(CharSequence csq, int start, int end)
58 throws IOException {
59 ss.send(csq.subSequence(start, end));
60 return this;
61 }
62
63 @Override
64 public void close() throws IOException {
65 ss.sendBlock();
66 }
67
68 }
69
70 /**
71 * The current version of the network protocol.
72 */
73 static public final int CURRENT_VERSION = 1;
74
75 /**
76 * The end of block marker.
77 *
78 * An end of block marker needs to be on a line on itself to be valid, and
79 * will denote the end of a block of data.
80 */
81 static private String EOB = ".";
82
83 private Socket s;
84 private PrintWriter out;
85 private BufferedReader in;
86 private int version; // version of the OTHER end, not this one (this one is
87 // CURRENT_VERSION obviously)
88
89 private String label; // can be used for debugging
90
91 /**
92 * Create a new {@link SimpleSocket} with the given {@link Socket}.
93 *
94 * @param s
95 * the {@link Socket}
96 */
97 public SimpleSocket(Socket s, String label) {
98 this.s = s;
99 this.label = label;
100 }
101
102 /**
103 * Return the label of this {@link SimpleSocket}. This is mainly used for
104 * debugging purposes or user display if any. It is optional.
105 *
106 * @return the label
107 */
108 public String getLabel() {
109 if (label == null)
110 return "[no name]";
111 return label;
112 }
113
114 /**
115 * Open the {@link SimpleSocket} for reading/writing and negotiates the
116 * version.
117 *
118 * Note that you <b>MUST</b> call {@link SimpleSocket#close()} when you are
119 * done to release the acquired resources.
120 *
121 * @param client
122 * TRUE for clients, FALSE for servers (server speaks first)
123 *
124 * @throws IOException
125 * in case of IO error
126 */
127 public void open(boolean client) throws IOException {
128 out = new PrintWriter(s.getOutputStream(), false);
129 in = new BufferedReader(new InputStreamReader(s.getInputStream()));
130
131 if (client) {
132 version = new CommandInstance(receiveLine(), -1).getVersion();
133 sendLine(new CommandInstance(Command.VERSION, CURRENT_VERSION)
134 .toString());
135 } else {
136 send(new CommandInstance(Command.VERSION, CURRENT_VERSION)
137 .toString());
138 // TODO: i18n
139 send("[Some help info here]");
140 send("you need to reply with your VERSION + end of block");
141 send("please send HELP in a full block or help");
142 sendBlock();
143 version = new CommandInstance(receiveLine(), -1).getVersion();
144 }
145 }
146
147 /**
148 * Close the connection and release acquired resources.
149 *
150 * @return TRUE if everything was closed properly, FALSE if the connection
151 * was broken (in all cases, resources are released)
152 */
153 public boolean close() {
154 boolean broken = false;
155
156 try {
157 sendBlock();
158 broken = out.checkError();
159 } catch (IOException e) {
160 broken = true;
161 }
162
163 try {
164 s.close();
165 } catch (IOException e) {
166 e.printStackTrace();
167 broken = true;
168 try {
169 in.close();
170 } catch (IOException ee) {
171 }
172 out.close();
173 }
174
175 s = null;
176 in = null;
177 out = null;
178
179 return !broken;
180 }
181
182 /**
183 * Sends lines to the remote server. Do <b>NOT</b> sends the end-of-block
184 * marker.
185 *
186 * @param data
187 * the data to send
188 *
189 * @throws IOException
190 * in case of IO error
191 */
192 protected void send(CharSequence data) throws IOException {
193 if (data != null) {
194 out.append(data);
195 }
196
197 out.append("\n");
198
199 if (out.checkError())
200 throw new IOException();
201 }
202
203 /**
204 * Sends an end-of-block marker to the remote server.
205 *
206 * @throws IOException
207 * in case of IO error
208 */
209 public void sendBlock() throws IOException {
210 sendBlock((List<String>) null);
211 }
212
213 /**
214 * Sends commands to the remote server, then sends an end-of-block marker.
215 *
216 * @param data
217 * the data to send
218 *
219 * @throws IOException
220 * in case of IO error
221 */
222 public void sendLine(String data) throws IOException {
223 sendBlock(Arrays.asList(new String[] { data }));
224 }
225
226 /**
227 * Sends commands to the remote server, then sends an end-of-block marker.
228 *
229 * @param data
230 * the data to send
231 *
232 * @throws IOException
233 * in case of IO error
234 */
235 public void sendBlock(List<String> data) throws IOException {
236 if (data != null) {
237 for (String dataLine : data) {
238 send(dataLine);
239 }
240 }
241
242 send(EOB);
243 }
244
245 /**
246 * Sends commands to the remote server, then sends an end-of-block marker.
247 *
248 * @param command
249 * the {@link Command} to send
250 *
251 * @throws IOException
252 * in case of IO error
253 */
254 public void sendCommand(Command command) throws IOException {
255 sendCommand(command, null);
256 }
257
258 /**
259 * Sends commands to the remote server, then sends an end-of-block marker.
260 *
261 * @param command
262 * the data to send
263 *
264 * @param param
265 * the parameter for this command if any
266 *
267 * @throws IOException
268 * in case of IO error
269 */
270 public void sendCommand(Command command, String param) throws IOException {
271 sendLine(new CommandInstance(command, param, CURRENT_VERSION)
272 .toString());
273 }
274
275 /**
276 * Create a new {@link Appendable} that can be used to send data on this
277 * {@link SimpleSocket}. When you are done, just call
278 * {@link BlockAppendable#close()}.
279 *
280 * @return the {@link Appendable}
281 */
282 public BlockAppendable createBlockAppendable() {
283 return new BlockAppendable(this);
284 }
285
286 /**
287 * Read a line from the remote server.
288 *
289 * Do <b>NOT</b> read until the end-of-block marker, and can return said
290 * block without conversion.
291 *
292 * @return the read line
293 *
294 * @throws IOException
295 * in case of IO error
296 */
297 protected String receive() throws IOException {
298 String line = in.readLine();
299 return line;
300 }
301
302 /**
303 * Read lines from the remote server until the end-of-block ("\0\n") marker
304 * is detected.
305 *
306 * @return the read lines without the end marker, or NULL if nothing more to
307 * read
308 *
309 * @throws IOException
310 * in case of IO error
311 */
312 public List<String> receiveBlock() throws IOException {
313 List<String> result = new LinkedList<String>();
314
315 String line = receive();
316 for (; line != null && !line.equals(EOB); line = receive()) {
317 result.add(line);
318 }
319
320 if (line == null)
321 return null;
322
323 return result;
324 }
325
326 /**
327 * Read a line from the remote server then read until the next end-of-block
328 * marker.
329 *
330 * @return the parsed line, or NULL if nothing more to read
331 *
332 * @throws IOException
333 * in case of IO error
334 */
335 public String receiveLine() throws IOException {
336 List<String> lines = receiveBlock();
337
338 if (lines.size() > 0)
339 return lines.get(0);
340
341 return null;
342 }
343
344 /**
345 * Read a line from the remote server and convert it to a
346 * {@link CommandInstance}, then read until the next end-of-block marker.
347 *
348 * @return the parsed {@link CommandInstance}
349 *
350 * @throws IOException
351 * in case of IO error
352 */
353 public CommandInstance receiveCommand() throws IOException {
354 String line = receive();
355 CommandInstance cmd = new CommandInstance(line, version);
356 receiveBlock();
357 return cmd;
358 }
359
360 @Override
361 public String toString() {
362 String source = "[not connected]";
363 InetAddress iadr = s.getInetAddress();
364 if (iadr != null)
365 source = iadr.getHostName();
366
367 return getLabel() + " (" + source + ")";
368 }
369 }