001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.camel.util; 018 019 import java.io.BufferedInputStream; 020 import java.io.BufferedOutputStream; 021 import java.io.BufferedReader; 022 import java.io.BufferedWriter; 023 import java.io.Closeable; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.io.OutputStream; 028 import java.io.Reader; 029 import java.io.UnsupportedEncodingException; 030 import java.io.Writer; 031 import java.nio.channels.FileChannel; 032 import java.nio.charset.Charset; 033 import java.nio.charset.UnsupportedCharsetException; 034 035 import org.apache.camel.Exchange; 036 import org.slf4j.Logger; 037 import org.slf4j.LoggerFactory; 038 039 /** 040 * IO helper class. 041 * 042 * @version 043 */ 044 public final class IOHelper { 045 046 private static final transient Logger LOG = LoggerFactory.getLogger(IOHelper.class); 047 private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; 048 private static final Charset UTF8_CHARSET = Charset.forName("UTF-8"); 049 050 private IOHelper() { 051 // Utility Class 052 } 053 054 /** 055 * Use this function instead of new String(byte[]) to avoid surprises from non-standard default encodings. 056 */ 057 public static String newStringFromBytes(byte[] bytes) { 058 try { 059 return new String(bytes, UTF8_CHARSET.name()); 060 } catch (UnsupportedEncodingException e) { 061 throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e); 062 } 063 } 064 065 /** 066 * Use this function instead of new String(byte[], int, int) 067 * to avoid surprises from non-standard default encodings. 068 */ 069 public static String newStringFromBytes(byte[] bytes, int start, int length) { 070 try { 071 return new String(bytes, start, length, UTF8_CHARSET.name()); 072 } catch (UnsupportedEncodingException e) { 073 throw new RuntimeException("Impossible failure: Charset.forName(\"UTF-8\") returns invalid name.", e); 074 } 075 } 076 077 /** 078 * Wraps the passed <code>in</code> into a {@link BufferedInputStream} 079 * object and returns that. If the passed <code>in</code> is already an 080 * instance of {@link BufferedInputStream} returns the same passed 081 * <code>in</code> reference as is (avoiding double wrapping). 082 * 083 * @param in the wrapee to be used for the buffering support 084 * @return the passed <code>in</code> decorated through a 085 * {@link BufferedInputStream} object as wrapper 086 */ 087 public static BufferedInputStream buffered(InputStream in) { 088 ObjectHelper.notNull(in, "in"); 089 return (in instanceof BufferedInputStream) ? (BufferedInputStream)in : new BufferedInputStream(in); 090 } 091 092 /** 093 * Wraps the passed <code>out</code> into a {@link BufferedOutputStream} 094 * object and returns that. If the passed <code>out</code> is already an 095 * instance of {@link BufferedOutputStream} returns the same passed 096 * <code>out</code> reference as is (avoiding double wrapping). 097 * 098 * @param out the wrapee to be used for the buffering support 099 * @return the passed <code>out</code> decorated through a 100 * {@link BufferedOutputStream} object as wrapper 101 */ 102 public static BufferedOutputStream buffered(OutputStream out) { 103 ObjectHelper.notNull(out, "out"); 104 return (out instanceof BufferedOutputStream) ? (BufferedOutputStream)out : new BufferedOutputStream(out); 105 } 106 107 /** 108 * Wraps the passed <code>reader</code> into a {@link BufferedReader} object 109 * and returns that. If the passed <code>reader</code> is already an 110 * instance of {@link BufferedReader} returns the same passed 111 * <code>reader</code> reference as is (avoiding double wrapping). 112 * 113 * @param reader the wrapee to be used for the buffering support 114 * @return the passed <code>reader</code> decorated through a 115 * {@link BufferedReader} object as wrapper 116 */ 117 public static BufferedReader buffered(Reader reader) { 118 ObjectHelper.notNull(reader, "reader"); 119 return (reader instanceof BufferedReader) ? (BufferedReader)reader : new BufferedReader(reader); 120 } 121 122 /** 123 * Wraps the passed <code>writer</code> into a {@link BufferedWriter} object 124 * and returns that. If the passed <code>writer</code> is already an 125 * instance of {@link BufferedWriter} returns the same passed 126 * <code>writer</code> reference as is (avoiding double wrapping). 127 * 128 * @param writer the wrapee to be used for the buffering support 129 * @return the passed <code>writer</code> decorated through a 130 * {@link BufferedWriter} object as wrapper 131 */ 132 public static BufferedWriter buffered(Writer writer) { 133 ObjectHelper.notNull(writer, "writer"); 134 return (writer instanceof BufferedWriter) ? (BufferedWriter)writer : new BufferedWriter(writer); 135 } 136 137 /** 138 * A factory method which creates an {@link IOException} from the given 139 * exception and message 140 * 141 * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0 142 */ 143 @Deprecated 144 public static IOException createIOException(Throwable cause) { 145 return createIOException(cause.getMessage(), cause); 146 } 147 148 /** 149 * A factory method which creates an {@link IOException} from the given 150 * exception and message 151 * 152 * @deprecated IOException support nested exception in Java 1.6. Will be removed in Camel 3.0 153 */ 154 @Deprecated 155 public static IOException createIOException(String message, Throwable cause) { 156 IOException answer = new IOException(message); 157 answer.initCause(cause); 158 return answer; 159 } 160 161 public static int copy(InputStream input, OutputStream output) throws IOException { 162 return copy(input, output, DEFAULT_BUFFER_SIZE); 163 } 164 165 public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException { 166 int avail = input.available(); 167 if (avail > 262144) { 168 avail = 262144; 169 } 170 if (avail > bufferSize) { 171 bufferSize = avail; 172 } 173 174 final byte[] buffer = new byte[bufferSize]; 175 int n = input.read(buffer); 176 int total = 0; 177 while (-1 != n) { 178 output.write(buffer, 0, n); 179 total += n; 180 n = input.read(buffer); 181 } 182 output.flush(); 183 return total; 184 } 185 186 public static void copyAndCloseInput(InputStream input, OutputStream output) throws IOException { 187 copyAndCloseInput(input, output, DEFAULT_BUFFER_SIZE); 188 } 189 190 public static void copyAndCloseInput(InputStream input, OutputStream output, int bufferSize) throws IOException { 191 copy(input, output, bufferSize); 192 close(input, null, LOG); 193 } 194 195 public static int copy(final Reader input, final Writer output, int bufferSize) throws IOException { 196 final char[] buffer = new char[bufferSize]; 197 int n = input.read(buffer); 198 int total = 0; 199 while (-1 != n) { 200 output.write(buffer, 0, n); 201 total += n; 202 n = input.read(buffer); 203 } 204 output.flush(); 205 return total; 206 } 207 208 /** 209 * Forces any updates to this channel's file to be written to the storage device that contains it. 210 * 211 * @param channel the file channel 212 * @param name the name of the resource 213 * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt> 214 */ 215 public static void force(FileChannel channel, String name, Logger log) { 216 try { 217 if (channel != null) { 218 channel.force(true); 219 } 220 } catch (Exception e) { 221 if (log == null) { 222 // then fallback to use the own Logger 223 log = LOG; 224 } 225 if (name != null) { 226 log.warn("Cannot force FileChannel: " + name + ". Reason: " + e.getMessage(), e); 227 } else { 228 log.warn("Cannot force FileChannel. Reason: " + e.getMessage(), e); 229 } 230 } 231 } 232 233 /** 234 * Forces any updates to a FileOutputStream be written to the storage device that contains it. 235 * 236 * @param os the file output stream 237 * @param name the name of the resource 238 * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt> 239 */ 240 public static void force(FileOutputStream os, String name, Logger log) { 241 try { 242 if (os != null) { 243 os.getFD().sync(); 244 } 245 } catch (Exception e) { 246 if (log == null) { 247 // then fallback to use the own Logger 248 log = LOG; 249 } 250 if (name != null) { 251 log.warn("Cannot sync FileDescriptor: " + name + ". Reason: " + e.getMessage(), e); 252 } else { 253 log.warn("Cannot sync FileDescriptor. Reason: " + e.getMessage(), e); 254 } 255 } 256 } 257 258 /** 259 * Closes the given writer, logging any closing exceptions to the given log. 260 * An associated FileOutputStream can optionally be forced to disk. 261 * 262 * @param writer the writer to close 263 * @param os an underlying FileOutputStream that will to be forced to disk according to the the force parameter 264 * @param name the name of the resource 265 * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt> 266 * @param force forces the FileOutputStream to disk 267 */ 268 public static void close(Writer writer, FileOutputStream os, String name, Logger log, boolean force) { 269 if (writer != null && force) { 270 // flush the writer prior to syncing the FD 271 try { 272 writer.flush(); 273 } catch (Exception e) { 274 if (log == null) { 275 // then fallback to use the own Logger 276 log = LOG; 277 } 278 if (name != null) { 279 log.warn("Cannot flush Writer: " + name + ". Reason: " + e.getMessage(), e); 280 } else { 281 log.warn("Cannot flush Writer. Reason: " + e.getMessage(), e); 282 } 283 } 284 force(os, name, log); 285 } 286 close(writer, name, log); 287 } 288 289 /** 290 * Closes the given resource if it is available, logging any closing exceptions to the given log. 291 * 292 * @param closeable the object to close 293 * @param name the name of the resource 294 * @param log the log to use when reporting closure warnings, will use this class's own {@link Logger} if <tt>log == null</tt> 295 */ 296 public static void close(Closeable closeable, String name, Logger log) { 297 if (closeable != null) { 298 try { 299 closeable.close(); 300 } catch (IOException e) { 301 if (log == null) { 302 // then fallback to use the own Logger 303 log = LOG; 304 } 305 if (name != null) { 306 log.warn("Cannot close: " + name + ". Reason: " + e.getMessage(), e); 307 } else { 308 log.warn("Cannot close. Reason: " + e.getMessage(), e); 309 } 310 } 311 } 312 } 313 314 /** 315 * Closes the given channel if it is available, logging any closing exceptions to the given log. 316 * The file's channel can optionally be forced to disk. 317 * 318 * @param channel the file channel 319 * @param name the name of the resource 320 * @param log the log to use when reporting warnings, will use this class's own {@link Logger} if <tt>log == null</tt> 321 * @param force forces the file channel to disk 322 */ 323 public static void close(FileChannel channel, String name, Logger log, boolean force) { 324 if (force) { 325 force(channel, name, log); 326 } 327 close(channel, name, log); 328 } 329 330 /** 331 * Closes the given resource if it is available. 332 * 333 * @param closeable the object to close 334 * @param name the name of the resource 335 */ 336 public static void close(Closeable closeable, String name) { 337 close(closeable, name, LOG); 338 } 339 340 /** 341 * Closes the given resource if it is available. 342 * 343 * @param closeable the object to close 344 */ 345 public static void close(Closeable closeable) { 346 close(closeable, null, LOG); 347 } 348 349 /** 350 * Closes the given resources if they are available. 351 * 352 * @param closeables the objects to close 353 */ 354 public static void close(Closeable... closeables) { 355 for (Closeable closeable : closeables) { 356 close(closeable); 357 } 358 } 359 360 public static void validateCharset(String charset) throws UnsupportedCharsetException { 361 if (charset != null) { 362 if (Charset.isSupported(charset)) { 363 Charset.forName(charset); 364 return; 365 } 366 } 367 throw new UnsupportedCharsetException(charset); 368 } 369 370 /** 371 * This method will take off the quotes and double quotes of the charset 372 */ 373 public static String normalizeCharset(String charset) { 374 if (charset != null) { 375 String answer = charset.trim(); 376 if (answer.startsWith("'") || answer.startsWith("\"")) { 377 answer = answer.substring(1); 378 } 379 if (answer.endsWith("'") || answer.endsWith("\"")) { 380 answer = answer.substring(0, answer.length() - 1); 381 } 382 return answer.trim(); 383 } else { 384 return null; 385 } 386 } 387 388 public static String getCharsetName(Exchange exchange) { 389 return getCharsetName(exchange, true); 390 } 391 392 /** 393 * Gets the charset name if set as property {@link Exchange#CHARSET_NAME}. 394 * 395 * @param exchange the exchange 396 * @param useDefault should we fallback and use JVM default charset if no property existed? 397 * @return the charset, or <tt>null</tt> if no found 398 */ 399 public static String getCharsetName(Exchange exchange, boolean useDefault) { 400 if (exchange != null) { 401 String charsetName = exchange.getProperty(Exchange.CHARSET_NAME, String.class); 402 if (charsetName != null) { 403 return IOHelper.normalizeCharset(charsetName); 404 } 405 } 406 if (useDefault) { 407 return getDefaultCharsetName(); 408 } else { 409 return null; 410 } 411 } 412 413 private static String getDefaultCharsetName() { 414 return ObjectHelper.getSystemProperty(Exchange.DEFAULT_CHARSET_PROPERTY, "UTF-8"); 415 } 416 }