2 * jFCPlib - FcpUtils.java - Copyright © 2008–2016 David Roden
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.
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.
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/>.
18 package net.pterodactylus.fcp;
20 import java.io.ByteArrayInputStream;
21 import java.io.ByteArrayOutputStream;
22 import java.io.Closeable;
23 import java.io.EOFException;
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;
35 * Helper class with utility methods for the FCP protocol.
37 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
39 public class FcpUtils {
41 /** Counter for unique identifiers. */
42 private static AtomicLong counter = new AtomicLong();
45 * Returns a unique identifier.
47 * @return A unique identifier
49 public static String getUniqueIdentifier() {
50 return new StringBuilder().append(System.currentTimeMillis()).append('-').append(counter.getAndIncrement()).toString();
54 * Parses an integer field, separated by ‘;’ and returns the parsed values.
58 * @return An array with the parsed values
59 * @throws NumberFormatException
60 * if a value can not be converted to a number
62 public static int[] decodeMultiIntegerField(String field) throws NumberFormatException {
63 StringTokenizer fieldTokens = new StringTokenizer(field, ";");
64 int[] result = new int[fieldTokens.countTokens()];
66 while (fieldTokens.hasMoreTokens()) {
67 String fieldToken = fieldTokens.nextToken();
68 result[counter++] = Integer.valueOf(fieldToken);
74 * Encodes the given integer array into a string, separating the values by
78 * The values to encode
79 * @return The encoded values
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(';');
87 encodedField.append(value);
89 return encodedField.toString();
93 * Encodes the given string array into a string, separating the values by
97 * The values to encode
98 * @return The encoded values
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(';');
106 encodedField.append(value);
108 return encodedField.toString();
112 * Tries to parse the given string into an int, returning <code>-1</code>
113 * if the string can not be parsed.
116 * The string to parse
117 * @return The parsed int, or <code>-1</code>
119 public static int safeParseInt(String value) {
120 return safeParseInt(value, -1);
124 * Tries to parse the given string into an int, returning
125 * <code>defaultValue</code> if the string can not be parsed.
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>
133 public static int safeParseInt(String value, int defaultValue) {
135 return Integer.valueOf(value);
136 } catch (NumberFormatException nfe1) {
142 * Tries to parse the given string into an long, returning <code>-1</code>
143 * if the string can not be parsed.
146 * The string to parse
147 * @return The parsed long, or <code>-1</code>
149 public static long safeParseLong(String value) {
150 return safeParseLong(value, -1);
154 * Tries to parse the given string into an long, returning
155 * <code>defaultValue</code> if the string can not be parsed.
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>
163 public static long safeParseLong(String value, long defaultValue) {
165 return Integer.valueOf(value);
166 } catch (NumberFormatException nfe1) {
172 * Closes the given socket.
175 * The socket to close
177 public static void close(Socket socket) {
178 if (socket != null) {
181 } catch (IOException ioe1) {
188 * Closes the given Closeable.
191 * The Closeable to close
193 public static void close(Closeable closeable) {
194 if (closeable != null) {
197 } catch (IOException ioe1) {
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
209 * The input stream to read from
211 * The output stream to write to
212 * @throws IOException
213 * if an I/O error occurs
215 public static void copy(InputStream source, OutputStream destination) throws IOException {
216 copy(source, destination, -1);
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
227 * The input stream to read from
229 * The output stream to write to
231 * The number of bytes to copy
232 * @throws IOException
233 * if an I/O error occurs
235 public static void copy(InputStream source, OutputStream destination, long length) throws IOException {
236 copy(source, destination, length, 1 << 16);
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
247 * The input stream to read from
249 * The output stream to write to
251 * The number of bytes to copy
254 * @throws IOException
255 * if an I/O error occurs
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];
261 while ((remaining == -1) || (remaining > 0)) {
262 read = source.read(buffer, 0, ((remaining > bufferSize) || (remaining == -1)) ? bufferSize : (int) remaining);
267 throw new EOFException("stream reached eof");
269 destination.write(buffer, 0, read);
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.
280 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
282 public static class TempInputStream extends InputStream {
284 /** The default maximum lenght for in-memory storage. */
285 public static final long MAX_LENGTH_MEMORY = 65536;
287 /** The temporary file to read from. */
288 private final File tempFile;
290 /** The input stream that reads from the file. */
291 private final InputStream fileInputStream;
293 /** The input stream that reads from memory. */
294 private final InputStream memoryInputStream;
297 * Creates a new temporary input stream that stores the given input
298 * stream in a temporary file.
300 * @param originalInputStream
301 * The original input stream
302 * @throws IOException
303 * if an I/O error occurs
305 public TempInputStream(InputStream originalInputStream) throws IOException {
306 this(originalInputStream, -1);
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.
314 * @param originalInputStream
315 * The original input stream
317 * The length of the input stream
318 * @throws IOException
319 * if an I/O error occurs
321 public TempInputStream(InputStream originalInputStream, long length) throws IOException {
322 this(originalInputStream, length, MAX_LENGTH_MEMORY);
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.
330 * @param originalInputStream
331 * The original input stream
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
339 public TempInputStream(InputStream originalInputStream, long length, long maxMemoryLength) throws IOException {
340 if ((length > -1) && (length <= maxMemoryLength)) {
341 ByteArrayOutputStream memoryOutputStream = new ByteArrayOutputStream((int) length);
343 FcpUtils.copy(originalInputStream, memoryOutputStream, length, (int) length);
345 memoryOutputStream.close();
348 fileInputStream = null;
349 memoryInputStream = new ByteArrayInputStream(memoryOutputStream.toByteArray());
351 tempFile = File.createTempFile("temp-", ".bin");
352 tempFile.deleteOnExit();
353 FileOutputStream fileOutputStream = null;
355 fileOutputStream = new FileOutputStream(tempFile);
356 FcpUtils.copy(originalInputStream, fileOutputStream);
357 fileInputStream = new FileInputStream(tempFile);
359 FcpUtils.close(fileOutputStream);
361 memoryInputStream = null;
369 public int available() throws IOException {
370 if (memoryInputStream != null) {
371 return memoryInputStream.available();
373 return fileInputStream.available();
380 public void close() throws IOException {
381 if (memoryInputStream != null) {
382 memoryInputStream.close();
386 fileInputStream.close();
393 public synchronized void mark(int readlimit) {
394 if (memoryInputStream != null) {
395 memoryInputStream.mark(readlimit);
398 fileInputStream.mark(readlimit);
405 public boolean markSupported() {
406 if (memoryInputStream != null) {
407 return memoryInputStream.markSupported();
409 return fileInputStream.markSupported();
416 public int read() throws IOException {
417 if (memoryInputStream != null) {
418 return memoryInputStream.read();
420 return fileInputStream.read();
427 public int read(byte[] b) throws IOException {
428 if (memoryInputStream != null) {
429 return memoryInputStream.read(b);
431 return fileInputStream.read(b);
438 public int read(byte[] b, int off, int len) throws IOException {
439 if (memoryInputStream != null) {
440 return memoryInputStream.read(b, off, len);
442 return fileInputStream.read(b, off, len);
449 public synchronized void reset() throws IOException {
450 if (memoryInputStream != null) {
451 memoryInputStream.reset();
454 fileInputStream.reset();
461 public long skip(long n) throws IOException {
462 if (memoryInputStream != null) {
463 return memoryInputStream.skip(n);
465 return fileInputStream.skip(n);