Avoid one case of getting out of synch and trying to read too much.
[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                         if (remaining > 0) {
272                                 remaining -= read;
273                         }
274                 }
275         }
276
277         /**
278          * This input stream stores the content of another input stream either in a
279          * file or in memory, depending on the length of the input stream.
280          *
281          * @author David ‘Bombe’ Roden &lt;bombe@freenetproject.org&gt;
282          */
283         public static class TempInputStream extends InputStream {
284
285                 /** The default maximum lenght for in-memory storage. */
286                 public static final long MAX_LENGTH_MEMORY = 65536;
287
288                 /** The temporary file to read from. */
289                 private final File tempFile;
290
291                 /** The input stream that reads from the file. */
292                 private final InputStream fileInputStream;
293
294                 /** The input stream that reads from memory. */
295                 private final InputStream memoryInputStream;
296
297                 /**
298                  * Creates a new temporary input stream that stores the given input
299                  * stream in a temporary file.
300                  *
301                  * @param originalInputStream
302                  *            The original input stream
303                  * @throws IOException
304                  *             if an I/O error occurs
305                  */
306                 public TempInputStream(InputStream originalInputStream) throws IOException {
307                         this(originalInputStream, -1);
308                 }
309
310                 /**
311                  * Creates a new temporary input stream that stores the given input
312                  * stream in memory if it is shorter than {@link #MAX_LENGTH_MEMORY},
313                  * otherwise it is stored in a file.
314                  *
315                  * @param originalInputStream
316                  *            The original input stream
317                  * @param length
318                  *            The length of the input stream
319                  * @throws IOException
320                  *             if an I/O error occurs
321                  */
322                 public TempInputStream(InputStream originalInputStream, long length) throws IOException {
323                         this(originalInputStream, length, MAX_LENGTH_MEMORY);
324                 }
325
326                 /**
327                  * Creates a new temporary input stream that stores the given input
328                  * stream in memory if it is shorter than <code>maxMemoryLength</code>,
329                  * otherwise it is stored in a file.
330                  *
331                  * @param originalInputStream
332                  *            The original input stream
333                  * @param length
334                  *            The length of the input stream
335                  * @param maxMemoryLength
336                  *            The maximum length to store in memory
337                  * @throws IOException
338                  *             if an I/O error occurs
339                  */
340                 public TempInputStream(InputStream originalInputStream, long length, long maxMemoryLength) throws IOException {
341                         if ((length > -1) && (length <= maxMemoryLength)) {
342                                 ByteArrayOutputStream memoryOutputStream = new ByteArrayOutputStream((int) length);
343                                 try {
344                                         FcpUtils.copy(originalInputStream, memoryOutputStream, length, (int) length);
345                                 } finally {
346                                         memoryOutputStream.close();
347                                 }
348                                 tempFile = null;
349                                 fileInputStream = null;
350                                 memoryInputStream = new ByteArrayInputStream(memoryOutputStream.toByteArray());
351                         } else {
352                                 tempFile = File.createTempFile("temp-", ".bin");
353                                 tempFile.deleteOnExit();
354                                 FileOutputStream fileOutputStream = null;
355                                 try {
356                                         fileOutputStream = new FileOutputStream(tempFile);
357                                         FcpUtils.copy(originalInputStream, fileOutputStream);
358                                         fileInputStream = new FileInputStream(tempFile);
359                                 } finally {
360                                         FcpUtils.close(fileOutputStream);
361                                 }
362                                 memoryInputStream = null;
363                         }
364                 }
365
366                 /**
367                  * {@inheritDoc}
368                  */
369                 @Override
370                 public int available() throws IOException {
371                         if (memoryInputStream != null) {
372                                 return memoryInputStream.available();
373                         }
374                         return fileInputStream.available();
375                 }
376
377                 /**
378                  * {@inheritDoc}
379                  */
380                 @Override
381                 public void close() throws IOException {
382                         if (memoryInputStream != null) {
383                                 memoryInputStream.close();
384                                 return;
385                         }
386                         tempFile.delete();
387                         fileInputStream.close();
388                 }
389
390                 /**
391                  * {@inheritDoc}
392                  */
393                 @Override
394                 public synchronized void mark(int readlimit) {
395                         if (memoryInputStream != null) {
396                                 memoryInputStream.mark(readlimit);
397                                 return;
398                         }
399                         fileInputStream.mark(readlimit);
400                 }
401
402                 /**
403                  * {@inheritDoc}
404                  */
405                 @Override
406                 public boolean markSupported() {
407                         if (memoryInputStream != null) {
408                                 return memoryInputStream.markSupported();
409                         }
410                         return fileInputStream.markSupported();
411                 }
412
413                 /**
414                  * {@inheritDoc}
415                  */
416                 @Override
417                 public int read() throws IOException {
418                         if (memoryInputStream != null) {
419                                 return memoryInputStream.read();
420                         }
421                         return fileInputStream.read();
422                 }
423
424                 /**
425                  * {@inheritDoc}
426                  */
427                 @Override
428                 public int read(byte[] b) throws IOException {
429                         if (memoryInputStream != null) {
430                                 return memoryInputStream.read(b);
431                         }
432                         return fileInputStream.read(b);
433                 }
434
435                 /**
436                  * {@inheritDoc}
437                  */
438                 @Override
439                 public int read(byte[] b, int off, int len) throws IOException {
440                         if (memoryInputStream != null) {
441                                 return memoryInputStream.read(b, off, len);
442                         }
443                         return fileInputStream.read(b, off, len);
444                 }
445
446                 /**
447                  * {@inheritDoc}
448                  */
449                 @Override
450                 public synchronized void reset() throws IOException {
451                         if (memoryInputStream != null) {
452                                 memoryInputStream.reset();
453                                 return;
454                         }
455                         fileInputStream.reset();
456                 }
457
458                 /**
459                  * {@inheritDoc}
460                  */
461                 @Override
462                 public long skip(long n) throws IOException {
463                         if (memoryInputStream != null) {
464                                 return memoryInputStream.skip(n);
465                         }
466                         return fileInputStream.skip(n);
467                 }
468
469         }
470
471 }