Refactor connection tests a bit more
[jFCPlib.git] / src / main / java / net / pterodactylus / fcp / FcpUtils.java
1 /*
2  * jFCPlib - FcpUtils.java - Copyright © 2008 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 2 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, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 package net.pterodactylus.fcp;
20
21 import java.io.ByteArrayInputStream;
22 import java.io.ByteArrayOutputStream;
23 import java.io.Closeable;
24 import java.io.EOFException;
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.OutputStream;
31 import java.net.Socket;
32 import java.util.StringTokenizer;
33 import java.util.concurrent.atomic.AtomicLong;
34
35 /**
36  * Helper class with utility methods for the FCP protocol.
37  *
38  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
39  */
40 public class FcpUtils {
41
42         /** Counter for unique identifiers. */
43         private static AtomicLong counter = new AtomicLong();
44
45         /**
46          * Returns a unique identifier.
47          *
48          * @return A unique identifier
49          */
50         public static String getUniqueIdentifier() {
51                 return new StringBuilder().append(System.currentTimeMillis()).append('-').append(counter.getAndIncrement()).toString();
52         }
53
54         /**
55          * Parses an integer field, separated by ‘;’ and returns the parsed values.
56          *
57          * @param field
58          *            The field to parse
59          * @return An array with the parsed values
60          * @throws NumberFormatException
61          *             if a value can not be converted to a number
62          */
63         public static int[] decodeMultiIntegerField(String field) throws NumberFormatException {
64                 StringTokenizer fieldTokens = new StringTokenizer(field, ";");
65                 int[] result = new int[fieldTokens.countTokens()];
66                 int counter = 0;
67                 while (fieldTokens.hasMoreTokens()) {
68                         String fieldToken = fieldTokens.nextToken();
69                         result[counter++] = Integer.valueOf(fieldToken);
70                 }
71                 return result;
72         }
73
74         /**
75          * Encodes the given integer array into a string, separating the values by
76          * ‘;’.
77          *
78          * @param values
79          *            The values to encode
80          * @return The encoded values
81          */
82         public static String encodeMultiIntegerField(int[] values) {
83                 StringBuilder encodedField = new StringBuilder();
84                 for (int value : values) {
85                         if (encodedField.length() > 0) {
86                                 encodedField.append(';');
87                         }
88                         encodedField.append(value);
89                 }
90                 return encodedField.toString();
91         }
92
93         /**
94          * Encodes the given string array into a string, separating the values by
95          * ‘;’.
96          *
97          * @param values
98          *            The values to encode
99          * @return The encoded values
100          */
101         public static String encodeMultiStringField(String[] values) {
102                 StringBuilder encodedField = new StringBuilder();
103                 for (String value : values) {
104                         if (encodedField.length() > 0) {
105                                 encodedField.append(';');
106                         }
107                         encodedField.append(value);
108                 }
109                 return encodedField.toString();
110         }
111
112         /**
113          * Tries to parse the given string into an int, returning <code>-1</code>
114          * if the string can not be parsed.
115          *
116          * @param value
117          *            The string to parse
118          * @return The parsed int, or <code>-1</code>
119          */
120         public static int safeParseInt(String value) {
121                 return safeParseInt(value, -1);
122         }
123
124         /**
125          * Tries to parse the given string into an int, returning
126          * <code>defaultValue</code> if the string can not be parsed.
127          *
128          * @param value
129          *            The string to parse
130          * @param defaultValue
131          *            The value to return if the string can not be parsed.
132          * @return The parsed int, or <code>defaultValue</code>
133          */
134         public static int safeParseInt(String value, int defaultValue) {
135                 try {
136                         return Integer.valueOf(value);
137                 } catch (NumberFormatException nfe1) {
138                         return defaultValue;
139                 }
140         }
141
142         /**
143          * Tries to parse the given string into an long, returning <code>-1</code>
144          * if the string can not be parsed.
145          *
146          * @param value
147          *            The string to parse
148          * @return The parsed long, or <code>-1</code>
149          */
150         public static long safeParseLong(String value) {
151                 return safeParseLong(value, -1);
152         }
153
154         /**
155          * Tries to parse the given string into an long, returning
156          * <code>defaultValue</code> if the string can not be parsed.
157          *
158          * @param value
159          *            The string to parse
160          * @param defaultValue
161          *            The value to return if the string can not be parsed.
162          * @return The parsed long, or <code>defaultValue</code>
163          */
164         public static long safeParseLong(String value, long defaultValue) {
165                 try {
166                         return Integer.valueOf(value);
167                 } catch (NumberFormatException nfe1) {
168                         return defaultValue;
169                 }
170         }
171
172         /**
173          * Closes the given socket.
174          *
175          * @param socket
176          *            The socket to close
177          */
178         public static void close(Socket socket) {
179                 if (socket != null) {
180                         try {
181                                 socket.close();
182                         } catch (IOException ioe1) {
183                                 /* ignore. */
184                         }
185                 }
186         }
187
188         /**
189          * Closes the given Closeable.
190          *
191          * @param closeable
192          *            The Closeable to close
193          */
194         public static void close(Closeable closeable) {
195                 if (closeable != null) {
196                         try {
197                                 closeable.close();
198                         } catch (IOException ioe1) {
199                                 /* ignore. */
200                         }
201                 }
202         }
203
204         /**
205          * Copies as many bytes as possible (i.e. until {@link InputStream#read()}
206          * returns <code>-1</code>) from the source input stream to the destination
207          * output stream.
208          *
209          * @param source
210          *            The input stream to read from
211          * @param destination
212          *            The output stream to write to
213          * @throws IOException
214          *             if an I/O error occurs
215          */
216         public static void copy(InputStream source, OutputStream destination) throws IOException {
217                 copy(source, destination, -1);
218         }
219
220         /**
221          * Copies <code>length</code> bytes from the source input stream to the
222          * destination output stream. If <code>length</code> is <code>-1</code> as
223          * much bytes as possible will be copied (i.e. until
224          * {@link InputStream#read()} returns <code>-1</code> to signal the end of
225          * the stream).
226          *
227          * @param source
228          *            The input stream to read from
229          * @param destination
230          *            The output stream to write to
231          * @param length
232          *            The number of bytes to copy
233          * @throws IOException
234          *             if an I/O error occurs
235          */
236         public static void copy(InputStream source, OutputStream destination, long length) throws IOException {
237                 copy(source, destination, length, 1 << 16);
238         }
239
240         /**
241          * Copies <code>length</code> bytes from the source input stream to the
242          * destination output stream. If <code>length</code> is <code>-1</code> as
243          * much bytes as possible will be copied (i.e. until
244          * {@link InputStream#read()} returns <code>-1</code> to signal the end of
245          * the stream).
246          *
247          * @param source
248          *            The input stream to read from
249          * @param destination
250          *            The output stream to write to
251          * @param length
252          *            The number of bytes to copy
253          * @param bufferSize
254          *            The buffer size
255          * @throws IOException
256          *             if an I/O error occurs
257          */
258         public static void copy(InputStream source, OutputStream destination, long length, int bufferSize) throws IOException {
259                 long remaining = length;
260                 byte[] buffer = new byte[bufferSize];
261                 int read = 0;
262                 while ((remaining == -1) || (remaining > 0)) {
263                         read = source.read(buffer, 0, ((remaining > bufferSize) || (remaining == -1)) ? bufferSize : (int) remaining);
264                         if (read == -1) {
265                                 if (length == -1) {
266                                         return;
267                                 }
268                                 throw new EOFException("stream reached eof");
269                         }
270                         destination.write(buffer, 0, read);
271                         remaining -= read;
272                 }
273         }
274
275         /**
276          * This input stream stores the content of another input stream either in a
277          * file or in memory, depending on the length of the input stream.
278          *
279          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
280          */
281         public static class TempInputStream extends InputStream {
282
283                 /** The default maximum lenght for in-memory storage. */
284                 public static final long MAX_LENGTH_MEMORY = 65536;
285
286                 /** The temporary file to read from. */
287                 private final File tempFile;
288
289                 /** The input stream that reads from the file. */
290                 private final InputStream fileInputStream;
291
292                 /** The input stream that reads from memory. */
293                 private final InputStream memoryInputStream;
294
295                 /**
296                  * Creates a new temporary input stream that stores the given input
297                  * stream in a temporary file.
298                  *
299                  * @param originalInputStream
300                  *            The original input stream
301                  * @throws IOException
302                  *             if an I/O error occurs
303                  */
304                 public TempInputStream(InputStream originalInputStream) throws IOException {
305                         this(originalInputStream, -1);
306                 }
307
308                 /**
309                  * Creates a new temporary input stream that stores the given input
310                  * stream in memory if it is shorter than {@link #MAX_LENGTH_MEMORY},
311                  * otherwise it is stored in a file.
312                  *
313                  * @param originalInputStream
314                  *            The original input stream
315                  * @param length
316                  *            The length of the input stream
317                  * @throws IOException
318                  *             if an I/O error occurs
319                  */
320                 public TempInputStream(InputStream originalInputStream, long length) throws IOException {
321                         this(originalInputStream, length, MAX_LENGTH_MEMORY);
322                 }
323
324                 /**
325                  * Creates a new temporary input stream that stores the given input
326                  * stream in memory if it is shorter than <code>maxMemoryLength</code>,
327                  * otherwise it is stored in a file.
328                  *
329                  * @param originalInputStream
330                  *            The original input stream
331                  * @param length
332                  *            The length of the input stream
333                  * @param maxMemoryLength
334                  *            The maximum length to store in memory
335                  * @throws IOException
336                  *             if an I/O error occurs
337                  */
338                 public TempInputStream(InputStream originalInputStream, long length, long maxMemoryLength) throws IOException {
339                         if ((length > -1) && (length <= maxMemoryLength)) {
340                                 ByteArrayOutputStream memoryOutputStream = new ByteArrayOutputStream((int) length);
341                                 try {
342                                         FcpUtils.copy(originalInputStream, memoryOutputStream, length, (int) length);
343                                 } finally {
344                                         memoryOutputStream.close();
345                                 }
346                                 tempFile = null;
347                                 fileInputStream = null;
348                                 memoryInputStream = new ByteArrayInputStream(memoryOutputStream.toByteArray());
349                         } else {
350                                 tempFile = File.createTempFile("temp-", ".bin");
351                                 tempFile.deleteOnExit();
352                                 FileOutputStream fileOutputStream = null;
353                                 try {
354                                         fileOutputStream = new FileOutputStream(tempFile);
355                                         FcpUtils.copy(originalInputStream, fileOutputStream);
356                                         fileInputStream = new FileInputStream(tempFile);
357                                 } finally {
358                                         FcpUtils.close(fileOutputStream);
359                                 }
360                                 memoryInputStream = null;
361                         }
362                 }
363
364                 /**
365                  * {@inheritDoc}
366                  */
367                 @Override
368                 public int available() throws IOException {
369                         if (memoryInputStream != null) {
370                                 return memoryInputStream.available();
371                         }
372                         return fileInputStream.available();
373                 }
374
375                 /**
376                  * {@inheritDoc}
377                  */
378                 @Override
379                 public void close() throws IOException {
380                         if (memoryInputStream != null) {
381                                 memoryInputStream.close();
382                                 return;
383                         }
384                         tempFile.delete();
385                         fileInputStream.close();
386                 }
387
388                 /**
389                  * {@inheritDoc}
390                  */
391                 @Override
392                 public synchronized void mark(int readlimit) {
393                         if (memoryInputStream != null) {
394                                 memoryInputStream.mark(readlimit);
395                                 return;
396                         }
397                         fileInputStream.mark(readlimit);
398                 }
399
400                 /**
401                  * {@inheritDoc}
402                  */
403                 @Override
404                 public boolean markSupported() {
405                         if (memoryInputStream != null) {
406                                 return memoryInputStream.markSupported();
407                         }
408                         return fileInputStream.markSupported();
409                 }
410
411                 /**
412                  * {@inheritDoc}
413                  */
414                 @Override
415                 public int read() throws IOException {
416                         if (memoryInputStream != null) {
417                                 return memoryInputStream.read();
418                         }
419                         return fileInputStream.read();
420                 }
421
422                 /**
423                  * {@inheritDoc}
424                  */
425                 @Override
426                 public int read(byte[] b) throws IOException {
427                         if (memoryInputStream != null) {
428                                 return memoryInputStream.read(b);
429                         }
430                         return fileInputStream.read(b);
431                 }
432
433                 /**
434                  * {@inheritDoc}
435                  */
436                 @Override
437                 public int read(byte[] b, int off, int len) throws IOException {
438                         if (memoryInputStream != null) {
439                                 return memoryInputStream.read(b, off, len);
440                         }
441                         return fileInputStream.read(b, off, len);
442                 }
443
444                 /**
445                  * {@inheritDoc}
446                  */
447                 @Override
448                 public synchronized void reset() throws IOException {
449                         if (memoryInputStream != null) {
450                                 memoryInputStream.reset();
451                                 return;
452                         }
453                         fileInputStream.reset();
454                 }
455
456                 /**
457                  * {@inheritDoc}
458                  */
459                 @Override
460                 public long skip(long n) throws IOException {
461                         if (memoryInputStream != null) {
462                                 return memoryInputStream.skip(n);
463                         }
464                         return fileInputStream.skip(n);
465                 }
466
467         }
468
469 }