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