Commit | Line | Data |
---|---|---|
a046fa49 NR |
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 | ||
a046fa49 NR |
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 | */ | |
e4444b0b | 25 | static public final int CURRENT_VERSION = 1; |
a046fa49 NR |
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) { | |
845fb1d7 NR |
84 | version = new CommandInstance(receiveLine(), -1).getVersion(); |
85 | sendLine(new CommandInstance(Command.VERSION, CURRENT_VERSION) | |
a046fa49 NR |
86 | .toString()); |
87 | } else { | |
845fb1d7 NR |
88 | send(new CommandInstance(Command.VERSION, CURRENT_VERSION) |
89 | .toString()); | |
a046fa49 NR |
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(); | |
845fb1d7 | 95 | version = new CommandInstance(receiveLine(), -1).getVersion(); |
a046fa49 NR |
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 | /** | |
845fb1d7 | 135 | * Sends lines to the remote server. Do <b>NOT</b> sends the end-of-block |
a046fa49 NR |
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 | * | |
845fb1d7 NR |
200 | * @param command |
201 | * the {@link Command} to send | |
a046fa49 NR |
202 | * |
203 | * @throws IOException | |
204 | * in case of IO error | |
205 | */ | |
845fb1d7 NR |
206 | public void sendCommand(Command command) throws IOException { |
207 | sendCommand(command, null); | |
a046fa49 NR |
208 | } |
209 | ||
210 | /** | |
211 | * Sends commands to the remote server, then sends an end-of-block marker. | |
212 | * | |
845fb1d7 | 213 | * @param command |
a046fa49 NR |
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 | */ | |
845fb1d7 NR |
222 | public void sendCommand(Command command, String param) throws IOException { |
223 | sendLine(new CommandInstance(command, param, CURRENT_VERSION).toString()); | |
a046fa49 NR |
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 | /** | |
845fb1d7 NR |
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. | |
a046fa49 | 287 | * |
845fb1d7 | 288 | * @return the parsed {@link CommandInstance} |
a046fa49 NR |
289 | * |
290 | * @throws IOException | |
291 | * in case of IO error | |
292 | */ | |
845fb1d7 | 293 | public CommandInstance receiveCommand() throws IOException { |
a046fa49 | 294 | String line = receive(); |
845fb1d7 | 295 | CommandInstance cmd = new CommandInstance(line, version); |
a046fa49 NR |
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 | } |