02e8c20aac5055672979d25a129b1ed78afc0040
[jFCPlib.git] / src / net / pterodactylus / fcp / FcpUtils.java
1 /*
2  * jSite2 - FcpUtils.java -
3  * Copyright © 2008 David Roden
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 package net.pterodactylus.fcp;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.Closeable;
25 import java.io.EOFException;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.FileOutputStream;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.net.Socket;
33 import java.util.StringTokenizer;
34 import java.util.concurrent.atomic.AtomicLong;
35
36 /**
37  * Helper class with utility methods for the FCP protocol.
38  * 
39  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
40  */
41 public class FcpUtils {
42
43         /** Counter for unique identifiers. */
44         private static AtomicLong counter = new AtomicLong();
45
46         /**
47          * Returns a unique identifier.
48          * 
49          * @return A unique identifier
50          */
51         public static String getUniqueIdentifier() {
52                 return new StringBuilder().append(System.currentTimeMillis()).append('-').append(counter.getAndIncrement()).toString();
53         }
54
55         /**
56          * Parses an integer field, separated by ‘;’ and returns the parsed values.
57          * 
58          * @param field
59          *            The field to parse
60          * @return An array with the parsed values
61          * @throws NumberFormatException
62          *             if a value can not be converted to a number
63          */
64         public static int[] decodeMultiIntegerField(String field) throws NumberFormatException {
65                 StringTokenizer fieldTokens = new StringTokenizer(field, ";");
66                 int[] result = new int[fieldTokens.countTokens()];
67                 int counter = 0;
68                 while (fieldTokens.hasMoreTokens()) {
69                         String fieldToken = fieldTokens.nextToken();
70                         result[counter++] = Integer.valueOf(fieldToken);
71                 }
72                 return result;
73         }
74
75         /**
76          * Encodes the given integer array into a string, separating the values by
77          * ‘;’.
78          * 
79          * @param values
80          *            The values to encode
81          * @return The encoded values
82          */
83         public static String encodeMultiIntegerField(int[] values) {
84                 StringBuilder encodedField = new StringBuilder();
85                 for (int value : values) {
86                         if (encodedField.length() > 0) {
87                                 encodedField.append(';');
88                         }
89                         encodedField.append(value);
90                 }
91                 return encodedField.toString();
92         }
93
94         /**
95          * Encodes the given string array into a string, separating the values by
96          * ‘;’.
97          * 
98          * @param values
99          *            The values to encode
100          * @return The encoded values
101          */
102         public static String encodeMultiStringField(String[] values) {
103                 StringBuilder encodedField = new StringBuilder();
104                 for (String value : values) {
105                         if (encodedField.length() > 0) {
106                                 encodedField.append(';');
107                         }
108                         encodedField.append(value);
109                 }
110                 return encodedField.toString();
111         }
112
113         /**
114          * Tries to parse the given string into an int, returning <code>-1</code> if
115          * the string can not be parsed.
116          * 
117          * @param value
118          *            The string to parse
119          * @return The parsed int, or <code>-1</code>
120          */
121         public static int safeParseInt(String value) {
122                 return safeParseInt(value, -1);
123         }
124
125         /**
126          * Tries to parse the given string into an int, returning
127          * <code>defaultValue</code> if the string can not be parsed.
128          * 
129          * @param value
130          *            The string to parse
131          * @param defaultValue
132          *            The value to return if the string can not be parsed.
133          * @return The parsed int, or <code>defaultValue</code>
134          */
135         public static int safeParseInt(String value, int defaultValue) {
136                 try {
137                         return Integer.valueOf(value);
138                 } catch (NumberFormatException nfe1) {
139                         return defaultValue;
140                 }
141         }
142
143         /**
144          * Tries to parse the given string into an long, returning <code>-1</code>
145          * if the string can not be parsed.
146          * 
147          * @param value
148          *            The string to parse
149          * @return The parsed long, or <code>-1</code>
150          */
151         public static long safeParseLong(String value) {
152                 return safeParseLong(value, -1);
153         }
154
155         /**
156          * Tries to parse the given string into an long, returning
157          * <code>defaultValue</code> if the string can not be parsed.
158          * 
159          * @param value
160          *            The string to parse
161          * @param defaultValue
162          *            The value to return if the string can not be parsed.
163          * @return The parsed long, or <code>defaultValue</code>
164          */
165         public static long safeParseLong(String value, long defaultValue) {
166                 try {
167                         return Integer.valueOf(value);
168                 } catch (NumberFormatException nfe1) {
169                         return defaultValue;
170                 }
171         }
172
173         /**
174          * Closes the given socket.
175          * 
176          * @param socket
177          *            The socket to close
178          */
179         public static void close(Socket socket) {
180                 if (socket != null) {
181                         try {
182                                 socket.close();
183                         } catch (IOException ioe1) {
184                                 /* ignore. */
185                         }
186                 }
187         }
188
189         /**
190          * Closes the given Closeable.
191          * 
192          * @param closeable
193          *            The Closeable to close
194          */
195         public static void close(Closeable closeable) {
196                 if (closeable != null) {
197                         try {
198                                 closeable.close();
199                         } catch (IOException ioe1) {
200                                 /* ignore. */
201                         }
202                 }
203         }
204
205         /**
206          * Copies as many bytes as possible (i.e. until {@link InputStream#read()}
207          * returns <code>-1</code>) from the source input stream to the destination
208          * output stream.
209          * 
210          * @param source
211          *            The input stream to read from
212          * @param destination
213          *            The output stream to write to
214          * @throws IOException
215          *             if an I/O error occurs
216          */
217         public static void copy(InputStream source, OutputStream destination) throws IOException {
218                 copy(source, destination, -1);
219         }
220
221         /**
222          * Copies <code>length</code> bytes from the source input stream to the
223          * destination output stream. If <code>length</code> is <code>-1</code> as
224          * much bytes as possible will be copied (i.e. until
225          * {@link InputStream#read()} returns <code>-1</code> to signal the end of
226          * the stream).
227          * 
228          * @param source
229          *            The input stream to read from
230          * @param destination
231          *            The output stream to write to
232          * @param length
233          *            The number of bytes to copy
234          * @throws IOException
235          *             if an I/O error occurs
236          */
237         public static void copy(InputStream source, OutputStream destination, long length) throws IOException {
238                 copy(source, destination, length, 1 << 16);
239         }
240
241         /**
242          * Copies <code>length</code> bytes from the source input stream to the
243          * destination output stream. If <code>length</code> is <code>-1</code> as
244          * much bytes as possible will be copied (i.e. until
245          * {@link InputStream#read()} returns <code>-1</code> to signal the end of
246          * the stream).
247          * 
248          * @param source
249          *            The input stream to read from
250          * @param destination
251          *            The output stream to write to
252          * @param length
253          *            The number of bytes to copy
254          * @param bufferSize
255          *            The buffer size
256          * @throws IOException
257          *             if an I/O error occurs
258          */
259         public static void copy(InputStream source, OutputStream destination, long length, int bufferSize) throws IOException {
260                 long remaining = length;
261                 byte[] buffer = new byte[bufferSize];
262                 int read = 0;
263                 while ((remaining == -1) || (remaining > 0)) {
264                         read = source.read(buffer, 0, ((remaining > bufferSize) || (remaining == -1)) ? bufferSize : (int) remaining);
265                         if (read == -1) {
266                                 if (length == -1) {
267                                         return;
268                                 }
269                                 throw new EOFException("stream reached eof");
270                         }
271                         destination.write(buffer, 0, read);
272                         remaining -= read;
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);
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 }