1 /** 2 * The utillib library. 3 * More information is available at http://www.jinchess.com/. 4 * Copyright (C) 2002 Alexander Maryanovsky. 5 * All rights reserved. 6 * 7 * The utillib library is free software; you can redistribute 8 * it and/or modify it under the terms of the GNU Lesser General Public License 9 * as published by the Free Software Foundation; either version 2 of the 10 * License, or (at your option) any later version. 11 * 12 * The utillib library is distributed in the hope that it will 13 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 15 * General Public License for more details. 16 * 17 * You should have received a copy of the GNU Lesser General Public License 18 * along with utillib library; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 */ 21 22 package free.util; 23 24 import java.io.*; 25 import java.net.MalformedURLException; 26 import java.net.URL; 27 import java.util.Hashtable; 28 import java.util.Properties; 29 30 31 /** 32 * Various utility methods that have something to do with I/O. 33 */ 34 35 public class IOUtilities{ 36 37 38 39 /** 40 * Maps URLs to byte arrays of the data loaded from them. 41 */ 42 43 private final static Hashtable urlCache = new Hashtable(); 44 45 46 47 /** 48 * Returns a DataOutputStream object based on the given OutputStream. 49 * If the given OutputStream is already an instance of DataOutputStream, 50 * the same (given) OutputStream is casted to DataOutputStream and returned, 51 * otherwise, a new wrapper DataOutputStream is created and returned. 52 */ 53 maybeCreateDataOutputStream(OutputStream out)54 public static DataOutputStream maybeCreateDataOutputStream(OutputStream out){ 55 if (out instanceof DataOutputStream) 56 return (DataOutputStream)out; 57 else 58 return new DataOutputStream(out); 59 } 60 61 62 63 /** 64 * Returns a DataInputStream object based on the given InputStream. 65 * If the given InputStream is already an instance of DataInputStream, 66 * the same (given) InputStream is casted to DataInputStream and returned, 67 * otherwise, a new wrapper DataInputStream is created and returned. 68 */ 69 maybeCreateDataInputStream(InputStream in)70 public static DataInputStream maybeCreateDataInputStream(InputStream in){ 71 if (in instanceof DataInputStream) 72 return (DataInputStream)in; 73 else 74 return new DataInputStream(in); 75 } 76 77 78 79 80 /** 81 * Copies all the files of the given source directory into the given 82 * destination directory, optionally recursively. 83 */ 84 copyDir(File source, File destination, boolean recurse)85 public static void copyDir(File source, File destination, boolean recurse) throws IOException{ 86 if (!source.exists()) 87 throw new IllegalArgumentException("The source directory ("+source+") doesn't exist"); 88 if (!source.isDirectory()) 89 throw new IllegalArgumentException("The source ("+source+") is a file, not a directory"); 90 if (!destination.exists()) 91 throw new IllegalArgumentException("The destination directory ("+destination+") doesn't exist"); 92 if (!destination.isDirectory()) 93 throw new IllegalArgumentException("The destination ("+destination+") is a file, not a directory"); 94 95 String [] filenames = source.list(); 96 for (int i=0; i<filenames.length; i++){ 97 String filename = filenames[i]; 98 File file = new File(source, filename); 99 if (file.isDirectory()){ 100 if (recurse){ 101 File destSubDir = new File(destination, filename); 102 if (!destSubDir.exists()) 103 if (!destSubDir.mkdirs()) 104 throw new IOException("Unable to create directory "+destSubDir); 105 106 copyDir(file, destSubDir, true); 107 } 108 } 109 else{ 110 InputStream in = null; 111 OutputStream out = null; 112 try{ 113 in = new FileInputStream(file); 114 out = new FileOutputStream(new File(destination, filename)); 115 pump(in, out); 116 } finally{ 117 if (in!=null) 118 in.close(); 119 if (out!=null) 120 out.close(); 121 } 122 123 } 124 } 125 } 126 127 128 129 130 /** 131 * Removes the given directory and all files within it, recursively. Returns 132 * <code>true</code> if successful, <code>false</code> otherwise. Note that if 133 * it return <code>false</code>, some (or all) the files in the directory may 134 * already be deleted. 135 */ 136 rmdir(File dir)137 public static boolean rmdir(File dir){ 138 if (!dir.isDirectory()) 139 throw new IllegalArgumentException(); 140 141 String [] filenames = dir.list(); 142 for (int i = 0; i < filenames.length; i++){ 143 File file = new File(dir, filenames[i]); 144 if (file.isDirectory()){ 145 if (!rmdir(file)) 146 return false; 147 } 148 else if (!file.delete()) 149 return false; 150 } 151 152 return dir.delete(); 153 } 154 155 156 157 158 /** 159 * Writes the bytes read from the given input stream into the given output 160 * stream until the end of the input stream is reached. Returns the amount of 161 * bytes actually read/written. 162 */ 163 pump(InputStream in, OutputStream out)164 public static int pump(InputStream in, OutputStream out) throws IOException{ 165 return pump(in, out, new byte[2048]); 166 } 167 168 169 170 171 /** 172 * Writes up to the given amount of bytes read from the given input stream 173 * into the given output stream until the end of the input stream is reached. 174 * Returns the amount of bytes actually read/written. 175 */ 176 pump(InputStream in, OutputStream out, int amount)177 public static int pump(InputStream in, OutputStream out, int amount) throws IOException{ 178 return pump(in, out, amount, new byte[2048]); 179 } 180 181 182 183 184 185 /** 186 * Writes the bytes read from the given input stream into the given output 187 * stream until the end of the input stream is reached. Returns the amount of 188 * bytes actually read/written. Uses the given byte array as the buffer. 189 */ 190 pump(InputStream in, OutputStream out, byte [] buf)191 public static int pump(InputStream in, OutputStream out, byte [] buf) throws IOException{ 192 if (buf.length==0) 193 throw new IllegalArgumentException("Cannot use a 0 length buffer"); 194 195 int count; 196 int amountRead = 0; 197 while ((count = in.read(buf))!=-1){ 198 out.write(buf,0,count); 199 amountRead += count; 200 } 201 202 return amountRead; 203 } 204 205 206 207 208 /** 209 * Writes up to the given amount of bytes read from the given input stream 210 * into the given output stream until the end of the input stream is reached. 211 * Returns the amount of bytes actually read/written. Uses the given byte array 212 * as the buffer. 213 */ 214 pump(InputStream in, OutputStream out, int amount, byte [] buf)215 public static int pump(InputStream in, OutputStream out, int amount, byte [] buf) throws IOException{ 216 if (buf.length == 0) 217 throw new IllegalArgumentException("Cannot use a 0 length buffer"); 218 219 int amountRead = 0; 220 while (amount > 0){ 221 int amountToRead = amount > buf.length ? buf.length : amount; 222 int count = in.read(buf, 0, amountToRead); 223 if (count==-1) 224 break; 225 226 out.write(buf,0,count); 227 amount -= count; 228 amountRead += count; 229 } 230 231 return amountRead; 232 } 233 234 235 236 237 238 /** 239 * Reads from the given InputStream until its end and returns a byte array 240 * of the contents. The input stream is not <code>close</code>d by this 241 * method. 242 */ 243 readToEnd(InputStream in)244 public static byte [] readToEnd(InputStream in) throws IOException{ 245 byte [] buf = new byte[2048]; 246 247 int amountRead = 0; 248 int count = 0; 249 while ((count = in.read(buf, amountRead, buf.length-amountRead)) > 0){ 250 amountRead += count; 251 252 if (amountRead == buf.length){ 253 byte [] oldBuf = buf; 254 buf = new byte[oldBuf.length*2]; 255 System.arraycopy(oldBuf, 0, buf, 0, amountRead); 256 } 257 } 258 259 byte [] arr = new byte[amountRead]; 260 System.arraycopy(buf, 0, arr, 0, amountRead); 261 return arr; 262 } 263 264 265 266 /** 267 * Reads the specified amount of bytes from the specified input stream and 268 * returns the resulting array. Throws an <code>EOFException</code> if the 269 * stream ends before the specified amount of bytes is read. 270 */ 271 read(InputStream in, int amount)272 public static byte [] read(InputStream in, int amount) throws IOException{ 273 ByteArrayOutputStream buf = new ByteArrayOutputStream(amount); 274 if (pump(in, buf, amount) != amount) 275 throw new EOFException(); 276 277 return buf.toByteArray(); 278 } 279 280 281 282 /** 283 * Loads and returns data from the specified URL. 284 */ 285 load(URL url, boolean allowCache)286 public static byte [] load(URL url, boolean allowCache) throws IOException{ 287 InputStream in = inputStreamForURL(url, allowCache); 288 try{ 289 return readToEnd(in); 290 } finally{ 291 try{ 292 in.close(); 293 } catch (IOException e){} 294 } 295 } 296 297 298 299 300 /** 301 * Reads all the information from the given InputStream and returns it as 302 * plain text by using the default system encoding. Note that this method 303 * doesn't close the given InputStream, that is left to the user. 304 */ 305 loadText(InputStream in)306 public static String loadText(InputStream in) throws IOException{ 307 return new String(readToEnd(in)); 308 } 309 310 311 312 313 /** 314 * Loads the text from the given URL and returns it as a string. 315 * 316 * @throws IOException if the given URL does not exist or an I/O error occurs 317 * while accessing it. 318 */ 319 loadText(URL url, boolean allowCache)320 public static String loadText(URL url, boolean allowCache) throws IOException{ 321 return new String(load(url, allowCache)); 322 } 323 324 325 326 327 /** 328 * Loads the given text file from the local drive, converts it to a String and 329 * returns the String. 330 * 331 * @throws IOException if the file does not exist or loading failed. 332 */ 333 loadTextFile(File file)334 public static String loadTextFile(File file) throws IOException{ 335 if (!file.exists()) 336 throw new IOException("File does not exist"); 337 338 InputStream in = new FileInputStream(file); 339 String text = loadText(in); 340 in.close(); 341 return text; 342 } 343 344 345 346 347 /** 348 * Loads a text file with the given name from the local drive, converts it to 349 * a String and returns the String. 350 * 351 * @throws IOException if the file does not exist or loading failed. 352 */ 353 loadTextFile(String filename)354 public static String loadTextFile(String filename) throws IOException{ 355 return loadTextFile(new File(filename)); 356 } 357 358 359 360 361 /** 362 * Compares the 2 given sub arrays. Returns true if they are equal, false 363 * otherwise. 364 * 365 * @throws ArrayIndexOutOfBounds if 366 * <UL> 367 * <LI> <code>offset1</code> or <code>offset2</code> are negative. 368 * <LI> length is negative. 369 * <LI> <code>offset1+length</code> is bigger than <code>arr1.length</code> 370 * <LI> <code>offset2+length</code> is bigger than <code>arr2.length</code> 371 * </UL> 372 */ 373 equal(byte [] arr1, int offset1, byte [] arr2, int offset2, int length)374 public static boolean equal(byte [] arr1, int offset1, byte [] arr2, int offset2, int length){ 375 if ((offset1<0)||(offset2<0)||(length<0)||(offset1+length>arr1.length)||(offset2+length>arr2.length)) 376 throw new ArrayIndexOutOfBoundsException(); 377 378 for (int i=0;i<length;i++){ 379 if (arr1[offset1+i]!=arr2[offset2+i]) 380 return false; 381 } 382 383 return true; 384 } 385 386 387 388 389 /** 390 * Returns a <code>URL</code> corresponding to the specified <code>File</code> 391 * or <code>null</code> if the <code>File</code> cannot be converted into a 392 * <code>URL</code>. 393 * NOTE: This is copied from the JDK1.3 source, File.java 394 */ 395 fileToURL(File file)396 public static URL fileToURL(File file){ 397 try{ 398 String path = file.getAbsolutePath(); 399 if (File.separatorChar != '/') 400 path = path.replace(File.separatorChar, '/'); 401 if (!path.startsWith("/")) 402 path = "/" + path; 403 if (!path.endsWith("/") && file.isDirectory()) 404 path = path + "/"; 405 return new URL("file", "", path); 406 } catch (MalformedURLException e){ 407 return null; 408 } 409 } 410 411 412 413 /** 414 * Creates and returns a new <code>java.util.Properties</code> object loaded 415 * from the specified <code>InputStream</code>. 416 */ 417 loadProperties(InputStream in)418 public static Properties loadProperties(InputStream in) throws IOException{ 419 return loadProperties(in, new Properties()); 420 } 421 422 423 424 /** 425 * Loads properties from the specified <code>InputStream</code> into the 426 * specified <code>Properties</code> object. Returns the passed 427 * <code>Properties</code> object. 428 */ 429 loadProperties(InputStream in, Properties props)430 public static Properties loadProperties(InputStream in, Properties props) throws IOException{ 431 if (in == null) 432 return null; 433 434 props.load(in); 435 436 return props; 437 } 438 439 440 441 442 /** 443 * Similar to the {@link #loadProperties(InputStream)} method, but closes 444 * the specified <code>InputStream</code> at the end of its operation. 445 */ 446 loadPropertiesAndClose(InputStream in)447 public static Properties loadPropertiesAndClose(InputStream in) throws IOException{ 448 return loadPropertiesAndClose(in, new Properties()); 449 } 450 451 452 453 /** 454 * Similar to the {@link #loadProperties(InputStream, Properties)} method, 455 * but closes the specified <code>InputStream</code> at the end of its 456 * operation. 457 */ 458 loadPropertiesAndClose(InputStream in, Properties props)459 public static Properties loadPropertiesAndClose(InputStream in, Properties props) throws IOException{ 460 try{ 461 return loadProperties(in, props); 462 } finally{ 463 try{ 464 in.close(); 465 } catch (IOException e){} 466 } 467 } 468 469 470 471 472 /** 473 * Creates and returns a new <code>java.util.Properties</code> object loaded 474 * from the specified <code>File</code>. 475 */ 476 loadProperties(File file)477 public static Properties loadProperties(File file) throws IOException{ 478 return loadPropertiesAndClose(new FileInputStream(file)); 479 } 480 481 482 483 /** 484 * Creates and returns a new <code>java.util.Properties</code> object loaded 485 * from the specified <code>URL</code>. 486 * <code>allowCache</code> specifies whether the data may be retrieved from 487 * the cache instead of being actually retrieved. 488 */ 489 loadProperties(URL url, boolean allowCache)490 public static Properties loadProperties(URL url, boolean allowCache) throws IOException{ 491 return loadProperties(url, allowCache, new Properties()); 492 } 493 494 495 496 /** 497 * Loads properties from the specified <code>URL</code> into the specified 498 * </code>Properties</code> object. Returns the passed 499 * <code>Properties</code> object. 500 * <code>allowCache</code> specifies whether the data may be retrieved from 501 * the cache instead of being actually retrieved. 502 */ 503 loadProperties(URL url, boolean allowCache, Properties props)504 public static Properties loadProperties(URL url, boolean allowCache, Properties props) throws IOException{ 505 return loadPropertiesAndClose(inputStreamForURL(url, allowCache), props); 506 } 507 508 509 510 /** 511 * Loads and caches the contents of the specified URL. Calls to any of the 512 * methods that load from URLs in this class will use the cached data. Calling 513 * this method with an already cached URL will cause it to be loaded again. If 514 * an <code>IOException</code> occurs while loading the data, the cache 515 * remains unchanged. 516 */ 517 cacheURL(URL url)518 public static void cacheURL(URL url) throws IOException{ 519 cacheData(url, load(url, false)); 520 } 521 522 523 524 /** 525 * Forces the data mapped to the specified URL to be the specified data. 526 * This method is useful when one part of an application wants to generate 527 * or specify data for another part. 528 */ 529 cacheData(URL url, byte [] data)530 public static void cacheData(URL url, byte [] data){ 531 urlCache.put(url, data); 532 } 533 534 535 536 /** 537 * Returns whether the specified URL is cached. 538 */ 539 isURLCached(URL url)540 public static boolean isURLCached(URL url){ 541 return urlCache.containsKey(url); 542 } 543 544 545 546 /** 547 * Returns an <code>InpuStream</code> for reading the data at the specified 548 * URL. If <code>allowCache</code> is <code>true</code>, and the URL is cached, 549 * a <code>ByteArrayInpuStream</code> with the cached data is returned. 550 */ 551 inputStreamForURL(URL url, boolean allowCache)552 public static InputStream inputStreamForURL(URL url, boolean allowCache) throws IOException{ 553 byte [] cached = null; 554 if (allowCache) 555 cached = (byte [])urlCache.get(url); 556 return cached == null ? url.openStream() : new ByteArrayInputStream(cached); 557 } 558 559 560 561 /** 562 * Loads data from the specified URLs asynchronously in a background thread. 563 * Once all the data is loaded, it is passed to the specified 564 * <code>DataReceiver</code>. <code>id</code> is a convenience allowing the 565 * receiver to identify the data - it is merely passed back to the receiver. 566 */ 567 loadAsynchronously(URL [] urls, Object id, DataReceiver receiver, boolean allowCache)568 public static void loadAsynchronously(URL [] urls, Object id, DataReceiver receiver, boolean allowCache){ 569 Thread asyncReader = 570 new Thread(new UrlDataReader((URL[])urls.clone(), id, receiver, allowCache), 571 "AsyncThread-" + (++UrlDataReader.threadCount)); 572 asyncReader.setDaemon(true); 573 asyncReader.start(); 574 } 575 576 577 578 /** 579 * Similar to <code>loadAsynchronously</code>, but returns only when all the 580 * data has been loaded and passed off to the receiver. 581 */ 582 loadSynchronously(URL [] urls, Object id, DataReceiver receiver, boolean allowCache)583 public static void loadSynchronously(URL [] urls, Object id, DataReceiver receiver, boolean allowCache){ 584 new UrlDataReader((URL[])urls.clone(), id, receiver, allowCache).run(); 585 } 586 587 588 589 /** 590 * The callback interface for asynchronous reading of data. 591 */ 592 593 public static interface DataReceiver{ 594 595 596 597 /** 598 * Gets called when all the data is loaded. 599 * The <code>IOException</code> array holds the exceptions thrown while 600 * loading. The indices in all the arrays correspond. 601 */ 602 dataRead(URL [] urls, Object id, byte [][] data, IOException [] exceptions)603 void dataRead(URL [] urls, Object id, byte [][] data, IOException [] exceptions); 604 605 606 607 } 608 609 610 611 /** 612 * Reads data from URLs. 613 */ 614 615 private static class UrlDataReader implements Runnable{ 616 617 618 619 /** 620 * The number of <code>Threads</code> running <code>UrlDataReader</code>s 621 * already created. 622 */ 623 624 public static int threadCount = 0; 625 626 627 628 /** 629 * The URLs to load data from. 630 */ 631 632 private final URL [] urls; 633 634 635 636 /** 637 * The identifier of this download. 638 */ 639 640 private final Object id; 641 642 643 644 /** 645 * The callback <code>DataReceiver</code>. 646 */ 647 648 private final DataReceiver receiver; 649 650 651 652 /** 653 * Whether it is allowed for the data to be retrieved from cache. 654 */ 655 656 private final boolean allowCache; 657 658 659 660 /** 661 * The data. 662 */ 663 664 private final byte [][] data; 665 666 667 668 /** 669 * The <code>IOExceptions</code> thrown while loading the data. 670 */ 671 672 private final IOException [] exceptions; 673 674 675 676 /** 677 * Creates a new <code>UrlDataReader</code> with the specified id, to load 678 * data from the specified URLs and report back to the specified 679 * <code>DataReceiver</code>. 680 */ 681 UrlDataReader(URL [] urls, Object id, DataReceiver receiver, boolean allowCache)682 public UrlDataReader(URL [] urls, Object id, DataReceiver receiver, boolean allowCache){ 683 this.urls = urls; 684 this.id = id; 685 this.receiver = receiver; 686 this.allowCache = allowCache; 687 this.data = new byte[urls.length][]; 688 this.exceptions = new IOException[urls.length]; 689 } 690 691 692 693 /** 694 * Reads the data and reports back to the receiver. 695 */ 696 run()697 public void run(){ 698 for (int i = 0; i < urls.length; i++){ 699 try{ 700 data[i] = load(urls[i], allowCache); 701 } catch (IOException e){ 702 exceptions[i] = e; 703 } 704 } 705 706 receiver.dataRead(urls, id, data, exceptions); 707 } 708 709 710 711 } 712 713 714 715 } 716