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