View Javadoc

1   /* This file is part of the project "Hilbert II" - http://www.qedeq.org" target="alexandria_uri">http://www.qedeq.org
2    *
3    * Copyright 2000-2014,  Michael Meyling <mime@qedeq.org>.
4    *
5    * "Hilbert II" is free software; you can redistribute
6    * it and/or modify it under the terms of the GNU General Public
7    * License as published by the Free Software Foundation; either
8    * version 2 of the License, or (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  
16  package org.qedeq.base.io;
17  
18  import java.io.BufferedOutputStream;
19  import java.io.BufferedReader;
20  import java.io.BufferedWriter;
21  import java.io.ByteArrayInputStream;
22  import java.io.File;
23  import java.io.FileFilter;
24  import java.io.FileInputStream;
25  import java.io.FileOutputStream;
26  import java.io.FileReader;
27  import java.io.FileWriter;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.InputStreamReader;
31  import java.io.OutputStream;
32  import java.io.OutputStreamWriter;
33  import java.io.Reader;
34  import java.io.UnsupportedEncodingException;
35  import java.io.Writer;
36  import java.net.URL;
37  import java.nio.charset.Charset;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.Enumeration;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Properties;
45  import java.util.StringTokenizer;
46  import java.util.TreeMap;
47  
48  import org.apache.commons.lang.SystemUtils;
49  
50  
51  /**
52   * A collection of useful static methods for input and output.
53   *
54   * LATER mime 20070101: use StringBuilder instead of StringBuffer if working under JDK 1.5
55   *
56   * @author  Michael Meyling
57   */
58  public final class IoUtility {
59  
60      /**
61       * Constructor, should never be called.
62       */
63      private IoUtility() {
64          // don't call me
65      }
66  
67      /**
68       * Get default encoding for this system.
69       *
70       * @return  Default encoding for this system.
71       */
72      public static String getDefaultEncoding() {
73          return SystemUtils.FILE_ENCODING;
74  // mime 20090630: under ubuntu the following gave the encoding ASCII:
75  //        return new InputStreamReader(
76  //              new ByteArrayInputStream(new byte[0])).getEncoding();
77  // but it was: file.encoding="ANSI_X3.41968"
78      }
79  
80      /**
81       * Get working Java encoding.
82       *
83       * @param   encoding    Try this encoding.
84       * @return              This is <code>encoding</code> if it is supported. Or an other
85       *                      encoding that is supported by this system.
86       */
87      public static String getWorkingEncoding(final String encoding) {
88          if (encoding != null) {
89              try {
90                  if (Charset.isSupported(encoding)
91                          && Charset.forName(encoding).canEncode()) {
92                      return encoding;
93                  }
94              } catch (RuntimeException e) {
95                  // ignore
96              }
97          }
98          // we must inform someone, but using
99          // Trace within this class is not wise, because it is used
100         // before the Trace is initialized.
101         System.err.println("not supported encoding: " + encoding);
102         return "ISO-8859-1";    // every system must support this
103     }
104 
105     /**
106      * Reads a file and returns the contents as a <code>String</code>.
107      *
108      * @param   filename    Name of the file (could include path).
109      * @param   encoding    Take this encoding.
110      * @return  Contents of file.
111      * @throws  IOException File exception occurred.
112      */
113     public static String loadFile(final String filename, final String encoding)
114             throws IOException {
115 
116         final StringBuffer buffer = new StringBuffer();
117         loadFile(filename, buffer, encoding);
118         return buffer.toString();
119     }
120 
121     /**
122      * Reads contents of a file into a string buffer.
123      *
124      * @param   filename    Name of the file (could include path).
125      * @param   buffer      Buffer to fill with file contents.
126      * @param   encoding    Take this encoding.
127      * @throws  IOException File exception occurred.
128      */
129     public static void loadFile(final String filename,
130             final StringBuffer buffer, final String encoding)
131             throws IOException {
132         loadFile(new File(filename), buffer, encoding);
133     }
134 
135     /**
136      * Reads contents of a stream into a string buffer. Stream is not closed.
137      *
138      * @param   in          This stream will be loaded.
139      * @param   buffer      Buffer to fill with file contents.
140      * @throws  IOException File exception occurred.
141      *
142      * @deprecated  Use {@link #loadReader(Reader, StringBuffer)}.
143      */
144     public static void loadStream(final InputStream in, final StringBuffer buffer)
145             throws IOException {
146 
147         buffer.setLength(0);
148         int c;
149         while ((c = in.read()) >= 0) {
150             buffer.append((char) c);
151         }
152     }
153 
154     /**
155      * Returns contents of a stream into a string, respecting a maximum length.
156      * No exceptions are thrown. Stream is not closed.
157      *
158      * @param   in          This stream will be loaded.
159      * @param   maxLength   This length is not exceeded.
160      * @return  readData    Data read, is not <code>null</code>.
161      */
162     public static String loadStreamWithoutException(final InputStream in, final int maxLength) {
163 
164         if (in == null) {
165             return "";
166         }
167         final StringBuffer buffer = new StringBuffer();
168         buffer.setLength(0);
169         try {
170             int counter = 0;
171             int c;
172             while (counter++ < maxLength) {
173                 c = in.read();
174                 if (c < 0) {
175                     break;
176                 }
177                 buffer.append((char) c);
178             }
179         } catch (IOException e) {
180             // ignored
181         } catch (RuntimeException e) {
182             // ignored
183         }
184         return buffer.toString();
185     }
186 
187     /**
188      * Reads contents of a {@link Reader} into a string buffer. Reader is not closed.
189      *
190      * @param   in          This reader will be loaded.
191      * @param   buffer      Buffer to fill with file contents.
192      * @throws  IOException File exception occurred.
193      */
194     public static void loadReader(final Reader in, final StringBuffer buffer)
195             throws IOException {
196 
197         buffer.setLength(0);
198         int c;
199         while ((c = in.read()) >= 0) {
200             buffer.append((char) c);
201         }
202     }
203 
204     /**
205      * Reads contents of a file into a string buffer. Uses default encoding.
206      *
207      * @param   file        This file will be loaded.
208      * @param   buffer      Buffer to fill with file contents.
209      * @throws  IOException File exception occurred.
210      *
211      * @deprecated  Use {@link #loadFile(File, StringBuffer, String)}.
212      */
213     public static void loadFile(final File file,
214             final StringBuffer buffer)
215             throws IOException {
216 
217         final int size = (int) file.length();
218         final char[] data = new char[size];
219         buffer.setLength(0);
220         FileReader in = null;
221         try {
222             in = new FileReader(file);
223             int charsread = 0;
224             while (charsread < size) {
225                 charsread += in.read(data, charsread, size - charsread);
226             }
227         } finally {
228             close(in);
229         }
230         buffer.insert(0, data);
231     }
232 
233     /**
234      * Reads contents of a file into a string buffer.
235      *
236      * @param   file        This file will be loaded.
237      * @param   buffer      Buffer to fill with file contents.
238      * @param   encoding    Take this encoding.
239      * @throws  IOException File exception occurred.
240      */
241     public static void loadFile(final File file,
242             final StringBuffer buffer, final String encoding)
243             throws IOException {
244 
245         buffer.setLength((int) file.length());    // ensure capacity
246         buffer.setLength(0);
247         final InputStreamReader in = new InputStreamReader(new FileInputStream(file), encoding);
248         final char[] data = new char[10 * 1024];
249 
250         try {
251             int charsread = 0;
252             while (0 < (charsread = in.read(data, 0, data.length))) {
253                 buffer.append(data, 0, charsread);
254             }
255         } finally {
256             in.close();
257         }
258     }
259 
260     /**
261      * Reads a file and returns the contents as a <code>String</code>.
262      *
263      * @param   file        File to load from.
264      * @return  Contents of file.
265      * @throws  IOException File exception occurred.
266      */
267     public static final byte[] loadFileBinary(final File file) throws IOException {
268         final int size = (int) file.length();
269         final FileInputStream in = new FileInputStream(file);
270         try {
271             final byte[] data = new byte[size];
272             int charsread = 0;
273             while (charsread < size) {
274                 final int read = in.read(data, charsread, size - charsread);
275                 if (read == -1) {
276                     final byte[] result = new byte[charsread];
277                     System.arraycopy(data, 0, result, 0, charsread);
278                     return result;
279                 }
280                 charsread += read;
281             }
282             in.close();
283             return data;
284         } finally {
285             close(in);
286         }
287     }
288 
289 
290     /**
291      * Reads contents of an URL into a string buffer. The filling is character set dependent.
292      * Content is added to the end of buffer. (Existing data is not cleared.)
293      * <p>
294      * All parameters should not be <code>null</code>.
295      * @param   url         This URL will be loaded.
296      * @param   buffer      Buffer to fill with file contents.
297      * @throws  IOException Reading failed.
298      *
299      * @deprecated  Choose correct encoding.
300      */
301     public static void loadFile(final URL url, final StringBuffer buffer) throws IOException {
302         InputStream in = null;
303         BufferedReader dis = null;
304         try {
305             in = url.openStream();
306             dis = new BufferedReader(new InputStreamReader(in));
307             int i;
308             while ((i = dis.read()) != -1) {
309                 buffer.append((char) i);
310             }
311         } finally {
312             close(in);
313             close(dis);
314         }
315     }
316 
317     /**
318      * Reads contents of an URL into a StringBuffer. The filling is character set dependent. The
319      * buffer is not cleared, contents is just added.
320      * <p>
321      * All parameters should not be <code>null</code>.
322      * @param   url         This URL will be loaded.
323      * @param   buffer      Buffer to fill with file contents.
324      * @param   encoding    Take this encoding.
325      * @throws  IOException Reading failed.
326      */
327     public static void loadFile(final URL url, final StringBuffer buffer, final String encoding)
328             throws IOException {
329         InputStream in = null;
330         BufferedReader dis = null;
331         try {
332             in = url.openStream();
333             dis = new BufferedReader(new InputStreamReader(in, encoding));
334             int i;
335             while ((i = dis.read()) != -1) {
336                 buffer.append((char) i);
337             }
338         } finally {
339             close(in);
340             close(dis);
341         }
342     }
343 
344     /**
345      * Save binary contents of an URL into a file. Existing files are overwritten.
346      *
347      * @param   url     This URL will be loaded.
348      * @param   file    Write into this file.
349      * @throws  IOException Reading or writing failed.
350      */
351     public static void saveFile(final URL url, final File file) throws IOException {
352         saveFile(url.openStream(), file);
353     }
354 
355     /**
356      * Save binary contents of an input stream into a file. The input stream is closed even
357      * if exceptions occur.  Existing files are overwritten.
358      * @param   in      Read this stream.
359      * @param   file    Write into this file.
360      *
361      * @throws  IOException Reading or writing failed.
362      */
363     public static void saveFile(final InputStream in, final File file) throws IOException {
364         createNecessaryDirectories(file);
365         FileOutputStream out = null;
366         try {
367             out = new FileOutputStream(file);
368             final byte[] data = new byte[8 * 1024];
369             int length;
370             while ((length = in.read(data)) != -1) {
371                 out.write(data, 0, length);
372             }
373         } finally {
374             close(in);
375             close(out);
376         }
377     }
378 
379     /**
380      * Convert String into a {@link Reader}.
381      *
382      * <a href="http://bugs.sun.com/bugdatabase/view_bug.do;:WuuT?bug_id=4094886">
383      * Bug ID: 4094886</a>
384      *
385      * @param   data    Convert this.
386      * @return  Resulting reader.
387      */
388     public static final Reader stringToReader(final String data) {
389         try {
390             return new InputStreamReader(new ByteArrayInputStream(data.getBytes("ISO-8859-1")));
391         } catch (UnsupportedEncodingException e) {
392             // should never occur
393             throw new RuntimeException(e);
394         }
395     }
396 
397     /**
398      * Saves a <code>String</code> into a file. Existing files are overwritten.
399      *
400      * @param   filename    Name of the file (could include path).
401      * @param   text        Data to save in the file.
402      * @throws  IOException File exception occurred.
403      *
404      * @deprecated  Use {@link #saveFile(File, String, String)} that has an encoding.
405      */
406     public static void saveFile(final String filename, final String text)
407             throws IOException {
408         saveFile(new File(filename), text);
409     }
410 
411     /**
412      * Saves a <code>StringBuffer</code> in a file. Existing files are overwritten.
413      *
414      * @param   filename    Name of the file (could include path).
415      * @param   text        Data to save in the file.
416      * @throws  IOException File exception occurred.
417      *
418      * @deprecated  Use {@link #saveFile(File, StringBuffer, String)} that has an encoding.
419      */
420     public static void saveFile(final String filename, final StringBuffer text)
421             throws IOException {
422         saveFile(new File(filename), text.toString());
423     }
424 
425     /**
426      * Saves a <code>StringBuffer</code> in a file. Existing files are overwritten.
427      *
428      * @param   file        File to save into.
429      * @param   text        Data to save in the file.
430      * @throws  IOException File exception occurred.
431      *
432      * @deprecated  Use {@link #saveFile(File, StringBuffer, String)} that has an encoding
433      * parameter.
434      */
435     public static void saveFile(final File file, final StringBuffer text)
436             throws IOException {
437         saveFile(file, text.toString());
438     }
439 
440     /**
441      * Saves a <code>String</code> in a file. Uses default encoding.  Existing files are
442      * overwritten.
443      *
444      * @param   file        File to save the data in.
445      * @param   text        Data to save in the file.
446      * @throws  IOException File exception occurred.
447      *
448      * @deprecated  Use {@link #saveFile(File, String, String)} that has an encoding parameter.
449      */
450     public static void saveFile(final File file, final String text)
451             throws IOException {
452         createNecessaryDirectories(file);
453         BufferedWriter out = null;
454         try {
455             out = new BufferedWriter(new FileWriter(file));
456             out.write(text);
457         } finally {
458             close(out);
459         }
460     }
461 
462     /**
463      * Saves a <code>String</code> in a file.  Existing files are overwritten.
464      *
465      * @param   file        File to save the data in.
466      * @param   text        Data to save in the file.
467      * @param   encoding    Use this encoding.
468      * @throws  IOException File exception occurred.
469      */
470     public static void saveFile(final File file, final StringBuffer text, final String encoding)
471             throws IOException {
472         saveFile(file, text.toString(), encoding);
473     }
474 
475     /**
476      * Saves a <code>String</code> in a file.
477      *
478      * @param   file        File to save the data in.
479      * @param   text        Data to save in the file.
480      * @param   encoding    Use this encoding.
481      * @throws  IOException File exception occurred.
482      */
483     public static void saveFile(final File file, final String text, final String encoding)
484             throws IOException {
485         createNecessaryDirectories(file);
486         BufferedWriter out = new BufferedWriter(
487             new OutputStreamWriter(new FileOutputStream(file), encoding));
488         try {
489             out.write(text);
490         } finally {
491             out.close();
492         }
493     }
494 
495     /**
496      * Saves a <code>data</code> in a file. Existing files are overwritten.
497      *
498      * @param   file        File to save the data in.
499      * @param   data        Data to save in the file.
500      * @throws  IOException File exception occurred.
501      */
502     public static void saveFileBinary(final File file, final byte[] data)
503             throws IOException {
504         createNecessaryDirectories(file);
505         BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
506         try {
507             out.write(data);
508         } finally {
509             out.close();
510         }
511     }
512 
513     /**
514      * Copies a file to a different location.
515      *
516      * @param   from    Copy source.
517      * @param   to      Copy destination.
518      * @throws  IOException File exception occurred.
519      */
520     public static void copyFile(final File from, final File to)
521             throws IOException {
522 
523         if (from.getCanonicalFile().equals(to.getCanonicalFile())) {
524             return;
525         }
526         createNecessaryDirectories(to);
527         FileInputStream in = null;
528         FileOutputStream out = null;
529         try {
530             in = new FileInputStream(from);
531             out = new FileOutputStream(to);
532 
533             byte[] data = new byte[8 * 1024];
534             int length;
535             while ((length = in.read(data)) != -1) {
536                 out.write(data, 0, length);
537             }
538         } finally {
539             close(in);
540             close(out);
541         }
542     }
543 
544     /**
545      * Copy one file or directory to another location.
546      * If targetLocation does not exist, it will be created.
547      *
548      * @param   sourceLocation  Copy from here. This can be a file or a directory.
549      * @param   targetLocation  Copy to this location. If source is a file this must be a file too.
550      * @throws  IOException     Something went wrong.
551      */
552     public static void copy(final String sourceLocation, final String targetLocation)
553             throws IOException {
554         copy(new File(sourceLocation), new File(targetLocation));
555     }
556 
557     /**
558      * Copy one directory to another location.
559      * If targetLocation does not exist, it will be created.
560      *
561      * @param   sourceLocation  Copy from here.
562      * @param   targetLocation  Copy to this location
563      * @throws  IOException     Something went wrong.
564      */
565     public static void copy(final File sourceLocation, final File targetLocation)
566             throws IOException {
567 
568         if (sourceLocation.isDirectory()) {
569             if (!targetLocation.exists()) {
570                 targetLocation.mkdir();
571             }
572             String[] children = sourceLocation.list();
573             for (int i = 0; i < children.length; i++) { // recursive call for all children
574                 copy(new File(sourceLocation, children[i]),
575                         new File(targetLocation, children[i]));
576             }
577         } else {    // copy file
578             copyFile(sourceLocation, targetLocation);
579         }
580     }
581 
582     /**
583      * List all matching files. Searches all matching sub directories recursively.
584      * Remember to return <code>true</code> for <code>accept(File pathname)</code> if
585      * <code>pathname</code> is a directory if you want to search all sub directories!
586      * If <code>sourceLocation</code> is a single file, this is the only file that will
587      * be in the resulting list.
588      *
589      * @param   sourceLocation  Check all files in this directory. (Or add this single file.)
590      * @param   filter          Accept only these directories and files.
591      * @return  List of matching files. Contains no directories.
592      * @throws  IOException     Something went wrong.
593      */
594     public static List listFilesRecursively(final File sourceLocation, final FileFilter filter)
595             throws IOException {
596         final List result = new ArrayList();
597         if (sourceLocation.isDirectory()) {
598             final File[] children = sourceLocation.listFiles();
599             for (int i = 0; i < children.length; i++) { // recursive call for all children
600                 result.addAll(listFilesRecursivelyIntern(children[i], filter));
601             }
602         } else {
603             result.add(sourceLocation);
604         }
605         return result;
606     }
607 
608     /**
609      * List all matching files. Searches all matching sub directories recursively.
610      * Remember to return <code>true</code> for <code>accept(File pathname)</code> if
611      * <code>pathname</code> is a directory if you want to search all sub directories!
612      *
613      * @param   sourceLocation  Check all files in this directory.
614      * @param   filter          Accept only these directories and files.
615      * @return  List of matching files. Contains no directories.
616      * @throws  IOException     Something went wrong.
617      */
618     private static List listFilesRecursivelyIntern(final File sourceLocation,
619             final FileFilter filter) throws IOException {
620         final List result = new ArrayList();
621         if (filter.accept(sourceLocation)) {
622             if (sourceLocation.isDirectory()) {
623                 File[] children = sourceLocation.listFiles();
624                 for (int i = 0; i < children.length; i++) { // recursive call for all children
625                     result.addAll(listFilesRecursivelyIntern(children[i], filter));
626                 }
627             } else {
628                 result.add(sourceLocation);
629             }
630         }
631         return result;
632     }
633 
634     /**
635      * Compare two files binary.
636      *
637      * @param   from    Compare source. This file must be <code>null</code> or be an existing file.
638      * @param   with    Compare with this file. This file must be <code>null</code> or be an
639      *                  existing file.
640      * @return  Is the contents of the two files binary equal?
641      * @throws  IOException File exception occurred.
642      */
643     public static boolean compareFilesBinary(final File from, final File with)
644             throws IOException {
645         if (from == null && with == null) {
646             return true;
647         }
648         if (from == null || with == null) {
649             return false;
650         }
651         if (from.getAbsoluteFile().equals(with.getAbsoluteFile())) {
652             return true;
653         }
654         if (from.length() != with.length()) {
655             return false;
656         }
657         byte[] dataOne = new byte[8 * 1024];
658         byte[] dataTwo = new byte[8 * 1024];
659         int length;
660 
661         FileInputStream one = null;
662         FileInputStream two = null;
663         try {
664             one = new FileInputStream(from);
665             two = new FileInputStream(with);
666 
667             while ((length = one.read(dataOne)) != -1) {
668                 if (length != two.read(dataTwo)) {
669                     return false;
670                 }
671                 if (!Arrays.equals(dataOne, dataTwo)) {
672                     return false;
673                 }
674             }
675             return true;
676         } finally {
677             close(one);
678             close(two);
679         }
680     }
681 
682     /**
683      * Compare two text files. Ignores different line separators. As there are:
684      * LF, CR, CR + LF, NEL, FF, LS, PS.
685      *
686      * @param   from        Compare source.
687      * @param   with        Compare with this file.
688      * @param   encoding    Use this character encoding. Must not be <code>null</code>.
689      * @return  Is the contents of the two text files equal?
690      * @throws  IOException File exception occurred or encoding is not supported.
691      * @throws  NullPointerException    Is encoding different from <code>null</code>?
692      */
693     public static boolean compareTextFiles(final File from, final File with, final String encoding)
694             throws IOException {
695         if (from == null && with == null) {
696             return true;
697         }
698         if (from == null || with == null) {
699             return false;
700         }
701         if (from.getAbsoluteFile().equals(with.getAbsoluteFile())) {
702             return true;
703         }
704 
705         BufferedReader one = null;
706         BufferedReader two = null;
707         FileInputStream fromIn = null;
708         FileInputStream withIn = null;
709         try {
710             fromIn = new FileInputStream(from);
711             one = new BufferedReader(new InputStreamReader(fromIn, encoding));
712             withIn = new FileInputStream(with);
713             two = new BufferedReader(new InputStreamReader(withIn, encoding));
714 
715             boolean crOne = false;
716             boolean crTwo = false;
717             do {
718                 int readOne = one.read();
719                 int readTwo = two.read();
720                 if (readOne == readTwo) {
721                     if (readOne < 0) {
722                         break;
723                     }
724                 } else {
725                     crOne = readOne == 0x0D;
726                     crTwo = readTwo == 0x0D;
727                     if (crOne) {
728                         readOne = one.read();
729                     }
730                     if (crTwo) {
731                         readTwo = two.read();
732                     }
733                     if (crOne && readOne != 0x0A && isCr(readTwo)) {
734                         readTwo = two.read();
735                     }
736                     if (crTwo && readTwo != 0x0A && isCr(readOne)) {
737                         readOne = one.read();
738                     }
739                     if (readOne != readTwo && (!isCr(readOne) && !isCr(readTwo))) {
740                         return false;
741                     }
742                 }
743             } while (true);
744             return true;
745         } finally {
746             close(fromIn);
747             close(one);
748             close(two);
749             close(withIn);
750         }
751     }
752 
753     /**
754      * Compare two text files. Ignores different line separators. As there are:
755      * LF, CR, CR + LF
756      *
757      * @param   from        Compare source.
758      * @param   with        Compare with this file.
759      * @param   startAtLine Start comparing at this line (beginning with 0).
760      * @param   encoding    Use this character encoding. Must not be <code>null</code>.
761      * @return  Is the contents of the two text files equal?
762      * @throws  IOException File exception occurred or encoding is not supported.
763      * @throws  NullPointerException    Is encoding different from <code>null</code>?
764      */
765     public static boolean compareTextFiles(final File from, final File with, final int startAtLine,
766             final String encoding) throws IOException {
767 
768         if (from == null && with == null) {
769             return true;
770         }
771         if (from == null || with == null) {
772             return false;
773         }
774         if (from.getAbsoluteFile().equals(with.getAbsoluteFile())) {
775             return true;
776         }
777         if (startAtLine < 0) {
778             return true;
779         }
780         BufferedReader one = null;
781         BufferedReader two = null;
782         FileInputStream fromIn = null;
783         FileInputStream withIn = null;
784         try {
785             fromIn = new FileInputStream(from);
786             one = new BufferedReader(new InputStreamReader(fromIn, encoding));
787             withIn = new FileInputStream(with);
788             two = new BufferedReader(new InputStreamReader(withIn, encoding));
789             int pos = 0;
790             do {
791                 String lineOne = one.readLine();
792                 String lineTwo = two.readLine();
793                 if (lineOne == null) {
794                     if (lineTwo == null) {
795                         break;
796                     }
797                     return false;
798                 }
799                 if (pos++ >= startAtLine && !lineOne.equals(lineTwo)) {
800                     return false;
801                 }
802             } while (true);
803             return true;
804         } finally {
805             close(fromIn);
806             close(one);
807             close(two);
808             close(withIn);
809         }
810     }
811 
812     /**
813      * Test if character is LF, CR, NEL, FF, LS, PS.
814      * @param   c   Character to test.
815      * @return  Is character a line terminator?
816      */
817     private static boolean isCr(final int c) {
818         return c == 0x0A || c == 0x0D || c == 0x85 || c == 0x0C || c == 0x2028 || c == 0x2029;
819     }
820 
821     /**
822      * Delete file directory recursive.
823      *
824      * @param   directory   Directory to delete. Must not be a symbolic link.
825      * @param   deleteDir   Delete directory itself too?
826      * @return  Was deletion successful?
827      */
828     public static boolean deleteDir(final File directory, final boolean deleteDir) {
829 
830         // first we check if the file is a symbolic link
831         try {
832             if (isSymbolicLink(directory)) {
833                 return false;
834             }
835         } catch (IOException e) {
836             return false;
837         }
838         final File candir;
839         try {
840             candir = directory.getCanonicalFile();
841         } catch (IOException e) {
842             return false;
843         }
844 
845         // now we go through all of the files and subdirectories in the
846         // directory and delete them one by one
847         boolean success = true;
848         File[] files = candir.listFiles();
849         if (files != null) {
850             for (int i = 0; i < files.length; i++) {
851                 File file = files[i];
852 
853                 // in case this directory is actually a symbolic link, or it's
854                 // empty, we want to try to delete the link before we try
855                 // anything
856                 boolean deleted = file.delete();
857                 if (!deleted) {
858                     // deleting the file failed, so maybe it's a non-empty
859                     // directory
860                     if (file.isDirectory()) {
861                         deleted = deleteDir(file, true);
862                     }
863 
864                     // otherwise, there's nothing else we can do
865                 }
866                 success = success && deleted;
867             }
868         }
869 
870         // now that we tried to clear the directory out, we can try to delete it
871         if (deleteDir && directory.exists()) {
872             return directory.delete();
873         }
874         return success;
875     }
876 
877     /**
878      * Delete directory contents for all files that match the filter. The main directory itself is
879      * not deleted.
880      *
881      * @param   directory   Directory to scan for files to delete.
882      * @param   filter      Filter files (and directories) to delete.
883      * @return  Was deletion successful?
884      */
885     public static boolean deleteDir(final File directory, final FileFilter filter) {
886         // first we check if the file is a symbolic link
887         try {
888             if (isSymbolicLink(directory)) {
889                 return false;
890             }
891         } catch (IOException e) {
892             return false;
893         }
894         final File candir;
895         try {
896             candir = directory.getCanonicalFile();
897         } catch (IOException e) {
898             return false;
899         }
900 
901         // now we go through all of the files and subdirectories in the
902         // directory and delete them one by one
903         boolean success = true;
904         File[] files = candir.listFiles(filter);
905         if (files != null) {
906             for (int i = 0; i < files.length; i++) {
907                 File file = files[i];
908 
909                 // in case this directory is actually a symbolic link, or it's
910                 // empty, we want to try to delete the link before we try
911                 // anything
912                 boolean deleted = file.delete();
913                 if (!deleted) {
914                     // deleting the file failed, so maybe it's a non-empty
915                     // directory
916                     if (file.isDirectory()) {
917                         deleted = deleteDir(file, true);
918                     }
919 
920                     // otherwise, there's nothing else we can do
921                 }
922                 success = success && deleted;
923             }
924         }
925 
926         return success;
927     }
928 
929     /**
930      * Determines whether the specified file is a symbolic link rather than an actual file.
931      * See {@link
932      * https://svn.apache.org/repos/asf/commons/proper/io/trunk/src/main/java/org/apache/commons/io/FileUtils.java}.
933      * @param   file    File to check.
934      * @return  Is the file is a symbolic link?
935      * @throws  IOException     IO error while checking the file.
936      */
937     public static boolean isSymbolicLink(final File file) throws IOException {
938         if (file == null) {
939             throw new NullPointerException("File must not be null");
940         }
941         // is windows file system in use?
942         if (File.separatorChar == '\\') {
943             // we have no symbolic links
944             return false;
945         }
946         File fileInCanonicalDir = null;
947         if (file.getParent() == null) {
948             fileInCanonicalDir = file;
949         } else {
950             File canonicalDir = file.getParentFile().getCanonicalFile();
951             fileInCanonicalDir = new File(canonicalDir, file.getName());
952         }
953         if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
954             return false;
955         }
956         return true;
957     }
958 
959     /**
960      * Print current system properties to System.out.
961      */
962     public static void printAllSystemProperties() {
963         Properties sysprops = System.getProperties();
964         for (Enumeration e = sysprops.propertyNames(); e.hasMoreElements(); ) {
965             String key = (String) e.nextElement();
966             String value = sysprops.getProperty(key);
967             System.out.println(key + "=" + value);
968         }
969     }
970 
971     /**
972      * Get home directory of user.
973      *
974      * @return  Home directory of user.
975      */
976     public static File getUserHomeDirectory() {
977         return new File((String) System.getProperties().get("user.home"));
978     }
979 
980     /**
981      * Creates necessary parent directories for a file.
982      *
983      * @param   file    File.
984      * @throws  IOException Creation failed.
985      */
986     public static void createNecessaryDirectories(final File file) throws IOException {
987         if (file != null && file.getParentFile() != null) {
988             file.getParentFile().mkdirs();
989             if (!file.getParentFile().exists()) {
990                 throw new IOException("directory creation failed: " + file.getParent());
991             }
992         }
993     }
994 
995     /**
996      * Create relative address from <code>origin</code> to <code>next</code>.
997      * The resulting file path has "/" as directory name separator.
998      * If the resulting file path is the same as origin specifies, we return "".
999      * Otherwise the result will always have an "/" as last character.
1000      *
1001      * @param   origin  This is the original location. Must be a directory.
1002      * @param   next    This should be the next location. Must also be a directory.
1003      * @return  Relative (or if necessary absolute) file path.
1004      */
1005     public static final String createRelativePath(final File origin, final File next) {
1006         if (origin.equals(next)) {
1007             return "";
1008         }
1009         final Path org = new Path(origin.getPath().replace(File.separatorChar, '/'), "");
1010         final Path ne = new Path(next.getPath().replace(File.separatorChar, '/'), "");
1011         return org.createRelative(ne.toString()).toString();
1012     }
1013 
1014     /**
1015      * Waits until a '\n' was read from System.in.
1016      */
1017     public static void waitln() {
1018         System.out.println("\n..press <return> to continue");
1019         try {
1020             (new java.io.BufferedReader(new java.io.InputStreamReader(
1021                 System.in))).readLine();
1022         } catch (IOException e) {
1023             // ignore
1024         }
1025     }
1026 
1027     /**
1028      * Closes input stream without exception.
1029      *
1030      * @param   in  Input stream, maybe <code>null</code>.
1031      */
1032     public static void close(final InputStream in) {
1033         if (in != null) {
1034             try {
1035                 in.close();
1036             } catch (Exception e) {
1037                 // ignore
1038             }
1039         }
1040     }
1041 
1042     /**
1043      * Closes writer without exception.
1044      *
1045      * @param   writer  Writer, maybe <code>null</code>.
1046      */
1047     public static void close(final Writer writer) {
1048         if (writer != null) {
1049             try {
1050                 writer.close();
1051             } catch (Exception e) {
1052                 // ignore
1053             }
1054         }
1055     }
1056 
1057     /**
1058      * Closes out stream without exception.
1059      *
1060      * @param   out Output stream, maybe <code>null</code>.
1061      */
1062     public static void close(final OutputStream out) {
1063         if (out != null) {
1064             try {
1065                 out.close();
1066             } catch (Exception e) {
1067                 // ignore
1068             }
1069         }
1070     }
1071 
1072     /**
1073      * Closes input reader without exception.
1074      *
1075      * @param   reader  Reader, maybe <code>null</code>.
1076      */
1077     public static void close(final Reader reader) {
1078         if (reader != null) {
1079             try {
1080                 reader.close();
1081             } catch (Exception e) {
1082                 // ignore
1083             }
1084         }
1085     }
1086 
1087     /**
1088      * Get start directory for application. Within the start directory all newly created data is
1089      * stored in. If this is no Java Webstart version the result is
1090      * <code>new File(".")</code>. Otherwise the start directory is the subdirectory
1091      * "." concatenated <code>application</code> within <code>user.home</code>.
1092      * If a system property <code>application + ".startDirectory"</code> is defined we take this
1093      * as the start directory.
1094      *
1095      * @param application   Application name, used for Java Webstart version. Should
1096      *                       be written in lowercase letters. A "." is automatically appended at
1097      *                       the beginning.
1098      * @return  Start directory for application.
1099      */
1100     public static final File getStartDirectory(final String application) {
1101         final File startDirectory;
1102         final String property = System.getProperty(application + ".startDirectory");
1103         if (property != null && property.length() > 0) {
1104             startDirectory = new File(property);
1105         } else if (isWebStarted()) {
1106             startDirectory = new File(getUserHomeDirectory(), "." + application);
1107         } else {
1108             startDirectory = new File(".");
1109         }
1110         return startDirectory;
1111     }
1112 
1113     /**
1114      * Was the application started by Java Webstart?
1115      *
1116      * @return  Was the application started by Java Webstart.
1117      */
1118     public static final boolean isWebStarted() {
1119         final String webStart = (String) System.getProperties().get("javawebstart.version");
1120         return webStart != null;
1121     }
1122 
1123     /**
1124      * Loads a property file from given URL.
1125      *
1126      * @param   url     URL to load properties from. Must not be <code>null</code>.
1127      * @return  Loaded properties.
1128      * @throws  IOException             Reading error.
1129      */
1130     public static Properties loadProperties(final URL url)
1131            throws IOException {
1132         Properties newprops = new Properties();
1133         InputStream in = null;
1134         try {
1135             in = url.openStream();
1136             newprops.load(in);
1137         } finally {
1138             close(in);
1139         }
1140         return newprops;
1141     }
1142 
1143     /**
1144      * Sleep my little class.
1145      *
1146      * @param   ms  Milliseconds to wait.
1147      */
1148     public static void sleep(final int ms) {
1149         final Object monitor = new Object();
1150         synchronized (monitor) {
1151             try {
1152                 monitor.wait(ms);
1153             } catch (InterruptedException e) {
1154             }
1155         }
1156     }
1157 
1158     /**
1159      * Get currently running java version and subversion numbers. This is the running JRE version.
1160      * If no version could be identified <code>null</code> is returned.
1161      *
1162      * @return  Array of version and subversion numbers.
1163      */
1164     public static int[] getJavaVersion() {
1165         final String version = System.getProperty("java.version");
1166         final List numbers = new ArrayList();
1167         final StringTokenizer tokenizer = new StringTokenizer(version, ".");
1168         while (tokenizer.hasMoreElements()) {
1169             String sub = tokenizer.nextToken();
1170             for (int i = 0; i < sub.length(); i++) {
1171                 if (!Character.isDigit(sub.charAt(i))) {
1172                     sub = sub.substring(0, i);
1173                     break;
1174                 }
1175             }
1176             try {
1177                 numbers.add(new Integer(Integer.parseInt(sub)));
1178             } catch (Exception e) {
1179                 e.printStackTrace();
1180                 break;
1181             }
1182         }
1183         if (numbers.size() == 0) {
1184             return null;
1185         }
1186         final int[] result = new int[numbers.size()];
1187         for (int i = 0; i < numbers.size(); i++) {
1188             result[i] = ((Integer) numbers.get(i)).intValue();
1189         }
1190         return result;
1191     }
1192 
1193     /**
1194      * Get key sorted list of all System Properties.
1195      *
1196      * @return  Array with the two columns key and value.
1197      */
1198     public static String[][] getSortedSystemProperties() {
1199         final Map map = new TreeMap(System.getProperties());
1200         String[][] rowData = new String[map.size()][2];
1201         int rowNum = 0;
1202         final Iterator iterator = map.entrySet().iterator();
1203         while (iterator.hasNext()) {
1204             Map.Entry entry = (Map.Entry) iterator.next();
1205             rowData[rowNum][0] = (String) entry.getKey();
1206             rowData[rowNum][1] = (String) entry.getValue();
1207             rowNum++;
1208         }
1209         return rowData;
1210     }
1211 
1212 }