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