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 }