Commit | Line | Data |
---|---|---|
a046fa49 NR |
1 | package be.nikiroo.jvcard.remote; |
2 | ||
3 | import java.io.BufferedReader; | |
59597d59 | 4 | import java.io.Closeable; |
a046fa49 NR |
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 | ||
a046fa49 NR |
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 { | |
59597d59 NR |
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 | ||
a046fa49 NR |
70 | /** |
71 | * The current version of the network protocol. | |
72 | */ | |
e4444b0b | 73 | static public final int CURRENT_VERSION = 1; |
a046fa49 NR |
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) { | |
845fb1d7 NR |
132 | version = new CommandInstance(receiveLine(), -1).getVersion(); |
133 | sendLine(new CommandInstance(Command.VERSION, CURRENT_VERSION) | |
a046fa49 NR |
134 | .toString()); |
135 | } else { | |
845fb1d7 NR |
136 | send(new CommandInstance(Command.VERSION, CURRENT_VERSION) |
137 | .toString()); | |
a046fa49 NR |
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(); | |
845fb1d7 | 143 | version = new CommandInstance(receiveLine(), -1).getVersion(); |
a046fa49 NR |
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 | /** | |
845fb1d7 | 183 | * Sends lines to the remote server. Do <b>NOT</b> sends the end-of-block |
a046fa49 NR |
184 | * marker. |
185 | * | |
186 | * @param data | |
187 | * the data to send | |
188 | * | |
189 | * @throws IOException | |
190 | * in case of IO error | |
191 | */ | |
59597d59 | 192 | protected void send(CharSequence data) throws IOException { |
a046fa49 | 193 | if (data != null) { |
59597d59 | 194 | out.append(data); |
a046fa49 NR |
195 | } |
196 | ||
59597d59 | 197 | out.append("\n"); |
a046fa49 NR |
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 | * | |
845fb1d7 NR |
248 | * @param command |
249 | * the {@link Command} to send | |
a046fa49 NR |
250 | * |
251 | * @throws IOException | |
252 | * in case of IO error | |
253 | */ | |
845fb1d7 NR |
254 | public void sendCommand(Command command) throws IOException { |
255 | sendCommand(command, null); | |
a046fa49 NR |
256 | } |
257 | ||
258 | /** | |
259 | * Sends commands to the remote server, then sends an end-of-block marker. | |
260 | * | |
845fb1d7 | 261 | * @param command |
a046fa49 NR |
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 | */ | |
845fb1d7 | 270 | public void sendCommand(Command command, String param) throws IOException { |
59597d59 NR |
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); | |
a046fa49 NR |
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 | /** | |
845fb1d7 NR |
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. | |
a046fa49 | 347 | * |
845fb1d7 | 348 | * @return the parsed {@link CommandInstance} |
a046fa49 NR |
349 | * |
350 | * @throws IOException | |
351 | * in case of IO error | |
352 | */ | |
845fb1d7 | 353 | public CommandInstance receiveCommand() throws IOException { |
a046fa49 | 354 | String line = receive(); |
845fb1d7 | 355 | CommandInstance cmd = new CommandInstance(line, version); |
a046fa49 NR |
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 | } |