From 1f46a0c3f84211dac3bed0139fdf530052872931 Mon Sep 17 00:00:00 2001 From: luffykou Date: Wed, 3 Aug 2016 16:29:35 +0800 Subject: [PATCH 1/4] change project to android studio type --- .gitignore | 5 + AndroidManifest.xml | 20 - app/.gitignore | 2 + app/build.gradle | 36 + app/proguard-rules.pro | 17 + app/src/main/AndroidManifest.xml | 18 + .../android/async/AsyncExecutor.java | 0 .../java}/com/litesuits/android/log/Log.java | 0 .../com/litesuits/android/log/LogReader.java | 0 .../com/litesuits/android/view/TipsView.java | 0 .../com/litesuits/common/assist/Averager.java | 140 +- .../com/litesuits/common/assist/Base64.java | 0 .../com/litesuits/common/assist/Check.java | 66 +- .../litesuits/common/assist/FlashLight.java | 0 .../litesuits/common/assist/KeyguardLock.java | 0 .../com/litesuits/common/assist/Network.java | 0 .../common/assist/SilentInstaller.java | 0 .../litesuits/common/assist/TimeAverager.java | 130 +- .../litesuits/common/assist/TimeCounter.java | 114 +- .../com/litesuits/common/assist/Toastor.java | 0 .../com/litesuits/common/assist/WakeLock.java | 0 .../com/litesuits/common/data/DataKeeper.java | 0 .../common/data/cipher/Base64Cipher.java | 0 .../litesuits/common/data/cipher/Cipher.java | 0 .../litesuits/common/data/cipher/Decrypt.java | 0 .../litesuits/common/data/cipher/Encrypt.java | 0 .../com/litesuits/common/io/Charsets.java | 74 +- .../com/litesuits/common/io/FileUtils.java | 5094 ++++++++--------- .../litesuits/common/io/FilenameUtils.java | 2126 +++---- .../com/litesuits/common/io/IOUtils.java | 4818 ++++++++-------- .../common/io/StringCodingUtils.java | 0 .../io/stream/ByteArrayOutputStream.java | 714 +-- .../common/io/stream/ClosedInputStream.java | 98 +- .../common/io/stream/StringBuilderWriter.java | 320 +- .../common/receiver/PhoneReceiver.java | 0 .../common/receiver/ScreenReceiver.java | 0 .../common/receiver/SmsReceiver.java | 0 .../common/receiver/TimeReceiver.java | 0 .../common/service/NotificationService.java | 0 .../com/litesuits/common/utils/AlarmUtil.java | 0 .../litesuits/common/utils/AndroidUtil.java | 0 .../com/litesuits/common/utils/AppUtil.java | 0 .../litesuits/common/utils/BitmapUtil.java | 0 .../com/litesuits/common/utils/ByteUtil.java | 0 .../com/litesuits/common/utils/ClassUtil.java | 142 +- .../litesuits/common/utils/ClipboardUtil.java | 0 .../com/litesuits/common/utils/CpuUtil.java | 0 .../litesuits/common/utils/DialogUtil.java | 0 .../litesuits/common/utils/DisplayUtil.java | 0 .../com/litesuits/common/utils/FieldUtil.java | 256 +- .../com/litesuits/common/utils/FileUtil.java | 0 .../litesuits/common/utils/HandlerUtil.java | 0 .../com/litesuits/common/utils/HexUtil.java | 0 .../common/utils/InputMethodUtils.java | 0 .../com/litesuits/common/utils/MD5Util.java | 0 .../litesuits/common/utils/MemoryUtil.java | 0 .../common/utils/NotificationUtil.java | 0 .../litesuits/common/utils/NumberUtil.java | 0 .../litesuits/common/utils/PackageUtil.java | 0 .../litesuits/common/utils/PollingUtil.java | 0 .../litesuits/common/utils/RandomUtil.java | 0 .../litesuits/common/utils/SdCardUtil.java | 0 .../com/litesuits/common/utils/ShellUtil.java | 0 .../litesuits/common/utils/TelephoneUtil.java | 0 .../litesuits/common/utils/VibrateUtil.java | 0 app/src/main/res/values/colors.xml | 2 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/styles.xml | 1 + build.gradle | 23 + gradle.properties | 18 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 53636 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 160 + gradlew.bat | 90 + jars/lite-common-1.1.1.jar | Bin 147948 -> 0 bytes jars/lite-common-1.1.3.jar | Bin 148124 -> 0 bytes litecommon1.0.0.jar | Bin 70127 -> 0 bytes res/values/strings.xml | 4 - settings.gradle | 1 + 79 files changed, 7428 insertions(+), 7070 deletions(-) delete mode 100644 AndroidManifest.xml create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml rename {src => app/src/main/java}/com/litesuits/android/async/AsyncExecutor.java (100%) rename {src => app/src/main/java}/com/litesuits/android/log/Log.java (100%) rename {src => app/src/main/java}/com/litesuits/android/log/LogReader.java (100%) rename {src => app/src/main/java}/com/litesuits/android/view/TipsView.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/Averager.java (95%) rename {src => app/src/main/java}/com/litesuits/common/assist/Base64.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/Check.java (94%) rename {src => app/src/main/java}/com/litesuits/common/assist/FlashLight.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/KeyguardLock.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/Network.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/SilentInstaller.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/TimeAverager.java (94%) rename {src => app/src/main/java}/com/litesuits/common/assist/TimeCounter.java (94%) rename {src => app/src/main/java}/com/litesuits/common/assist/Toastor.java (100%) rename {src => app/src/main/java}/com/litesuits/common/assist/WakeLock.java (100%) rename {src => app/src/main/java}/com/litesuits/common/data/DataKeeper.java (100%) rename {src => app/src/main/java}/com/litesuits/common/data/cipher/Base64Cipher.java (100%) rename {src => app/src/main/java}/com/litesuits/common/data/cipher/Cipher.java (100%) rename {src => app/src/main/java}/com/litesuits/common/data/cipher/Decrypt.java (100%) rename {src => app/src/main/java}/com/litesuits/common/data/cipher/Encrypt.java (100%) rename {src => app/src/main/java}/com/litesuits/common/io/Charsets.java (97%) rename {src => app/src/main/java}/com/litesuits/common/io/FileUtils.java (97%) rename {src => app/src/main/java}/com/litesuits/common/io/FilenameUtils.java (97%) rename {src => app/src/main/java}/com/litesuits/common/io/IOUtils.java (97%) rename {src => app/src/main/java}/com/litesuits/common/io/StringCodingUtils.java (100%) rename {src => app/src/main/java}/com/litesuits/common/io/stream/ByteArrayOutputStream.java (97%) rename {src => app/src/main/java}/com/litesuits/common/io/stream/ClosedInputStream.java (97%) rename {src => app/src/main/java}/com/litesuits/common/io/stream/StringBuilderWriter.java (96%) rename {src => app/src/main/java}/com/litesuits/common/receiver/PhoneReceiver.java (100%) rename {src => app/src/main/java}/com/litesuits/common/receiver/ScreenReceiver.java (100%) rename {src => app/src/main/java}/com/litesuits/common/receiver/SmsReceiver.java (100%) rename {src => app/src/main/java}/com/litesuits/common/receiver/TimeReceiver.java (100%) rename {src => app/src/main/java}/com/litesuits/common/service/NotificationService.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/AlarmUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/AndroidUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/AppUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/BitmapUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/ByteUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/ClassUtil.java (96%) rename {src => app/src/main/java}/com/litesuits/common/utils/ClipboardUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/CpuUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/DialogUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/DisplayUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/FieldUtil.java (96%) rename {src => app/src/main/java}/com/litesuits/common/utils/FileUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/HandlerUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/HexUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/InputMethodUtils.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/MD5Util.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/MemoryUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/NotificationUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/NumberUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/PackageUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/PollingUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/RandomUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/SdCardUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/ShellUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/TelephoneUtil.java (100%) rename {src => app/src/main/java}/com/litesuits/common/utils/VibrateUtil.java (100%) create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat delete mode 100644 jars/lite-common-1.1.1.jar delete mode 100644 jars/lite-common-1.1.3.jar delete mode 100644 litecommon1.0.0.jar delete mode 100644 res/values/strings.xml create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index fec3405..e6309d0 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,8 @@ gen/com/litesuits/common/BuildConfig.java lint.xml project.properties + +.gradle +/local.properties +/.idea +/build \ No newline at end of file diff --git a/AndroidManifest.xml b/AndroidManifest.xml deleted file mode 100644 index aef138d..0000000 --- a/AndroidManifest.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..d0b97c6 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +*.iml \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..a605d2d --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,36 @@ +apply plugin: 'com.android.library' +apply plugin: 'com.novoda.bintray-release' + +android { + compileSdkVersion 20 + buildToolsVersion "23.0.3" + + defaultConfig { + minSdkVersion 7 + targetSdkVersion 20 + versionCode 13 + versionName "1.1.3" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + lintOptions { + abortOnError false + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} + +publish { + userOrg = 'luffykou' //bintray.com username + groupId = 'com.luffykou' //jcenter url + artifactId = 'android-common-utils' //library name + publishVersion = '1.1.3' //version + desc = 'A powerful android common utils library.' //description + website = 'https://github.com/luffykou/android-common' //github url +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..c52d7e2 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/luffy/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..b737e4e --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + diff --git a/src/com/litesuits/android/async/AsyncExecutor.java b/app/src/main/java/com/litesuits/android/async/AsyncExecutor.java similarity index 100% rename from src/com/litesuits/android/async/AsyncExecutor.java rename to app/src/main/java/com/litesuits/android/async/AsyncExecutor.java diff --git a/src/com/litesuits/android/log/Log.java b/app/src/main/java/com/litesuits/android/log/Log.java similarity index 100% rename from src/com/litesuits/android/log/Log.java rename to app/src/main/java/com/litesuits/android/log/Log.java diff --git a/src/com/litesuits/android/log/LogReader.java b/app/src/main/java/com/litesuits/android/log/LogReader.java similarity index 100% rename from src/com/litesuits/android/log/LogReader.java rename to app/src/main/java/com/litesuits/android/log/LogReader.java diff --git a/src/com/litesuits/android/view/TipsView.java b/app/src/main/java/com/litesuits/android/view/TipsView.java similarity index 100% rename from src/com/litesuits/android/view/TipsView.java rename to app/src/main/java/com/litesuits/android/view/TipsView.java diff --git a/src/com/litesuits/common/assist/Averager.java b/app/src/main/java/com/litesuits/common/assist/Averager.java similarity index 95% rename from src/com/litesuits/common/assist/Averager.java rename to app/src/main/java/com/litesuits/common/assist/Averager.java index 131e986..8a7c93f 100755 --- a/src/com/litesuits/common/assist/Averager.java +++ b/app/src/main/java/com/litesuits/common/assist/Averager.java @@ -1,70 +1,70 @@ -package com.litesuits.common.assist; - -import com.litesuits.android.log.Log; - -import java.util.ArrayList; - -/** - * 用以统计平均数 - * - * @author MaTianyu - * 2013-12-11下午3:31:03 - */ -public class Averager { - private static final String TAG = "Averager"; - private ArrayList numList = new ArrayList(); - - /** - * 添加一个数字 - * - * @param num - */ - public synchronized void add(Number num) { - numList.add(num); - } - - /** - * 清除全部 - */ - public void clear() { - numList.clear(); - } - - /** - * 返回参与均值计算的数字个数 - * - * @return - */ - public Number size() { - return numList.size(); - } - - /** - * 获取平均数 - * - * @return - */ - public Number getAverage() { - if (numList.size() == 0) { - return 0; - } else { - Float sum = 0f; - for (int i = 0, size = numList.size(); i < size; i++) { - sum = sum.floatValue() + numList.get(i).floatValue(); - } - return sum / numList.size(); - } - } - - /** - * 打印数字列 - * - * @return - */ - public String print() { - String str = "PrintList(" + size() + "): " + numList; - Log.i(TAG, str); - return str; - } - -} +package com.litesuits.common.assist; + +import com.litesuits.android.log.Log; + +import java.util.ArrayList; + +/** + * 用以统计平均数 + * + * @author MaTianyu + * 2013-12-11下午3:31:03 + */ +public class Averager { + private static final String TAG = "Averager"; + private ArrayList numList = new ArrayList(); + + /** + * 添加一个数字 + * + * @param num + */ + public synchronized void add(Number num) { + numList.add(num); + } + + /** + * 清除全部 + */ + public void clear() { + numList.clear(); + } + + /** + * 返回参与均值计算的数字个数 + * + * @return + */ + public Number size() { + return numList.size(); + } + + /** + * 获取平均数 + * + * @return + */ + public Number getAverage() { + if (numList.size() == 0) { + return 0; + } else { + Float sum = 0f; + for (int i = 0, size = numList.size(); i < size; i++) { + sum = sum.floatValue() + numList.get(i).floatValue(); + } + return sum / numList.size(); + } + } + + /** + * 打印数字列 + * + * @return + */ + public String print() { + String str = "PrintList(" + size() + "): " + numList; + Log.i(TAG, str); + return str; + } + +} diff --git a/src/com/litesuits/common/assist/Base64.java b/app/src/main/java/com/litesuits/common/assist/Base64.java similarity index 100% rename from src/com/litesuits/common/assist/Base64.java rename to app/src/main/java/com/litesuits/common/assist/Base64.java diff --git a/src/com/litesuits/common/assist/Check.java b/app/src/main/java/com/litesuits/common/assist/Check.java similarity index 94% rename from src/com/litesuits/common/assist/Check.java rename to app/src/main/java/com/litesuits/common/assist/Check.java index f6ecb87..16c688c 100755 --- a/src/com/litesuits/common/assist/Check.java +++ b/app/src/main/java/com/litesuits/common/assist/Check.java @@ -1,33 +1,33 @@ -package com.litesuits.common.assist; - -import java.util.Collection; -import java.util.Map; - -/** - * 辅助判断 - * - * @author mty - * @date 2013-6-10下午5:50:57 - */ -public class Check { - - public static boolean isEmpty(CharSequence str) { - return isNull(str) || str.length() == 0; - } - - public static boolean isEmpty(Object[] os) { - return isNull(os) || os.length == 0; - } - - public static boolean isEmpty(Collection l) { - return isNull(l) || l.isEmpty(); - } - - public static boolean isEmpty(Map m) { - return isNull(m) || m.isEmpty(); - } - - public static boolean isNull(Object o) { - return o == null; - } -} +package com.litesuits.common.assist; + +import java.util.Collection; +import java.util.Map; + +/** + * 辅助判断 + * + * @author mty + * @date 2013-6-10下午5:50:57 + */ +public class Check { + + public static boolean isEmpty(CharSequence str) { + return isNull(str) || str.length() == 0; + } + + public static boolean isEmpty(Object[] os) { + return isNull(os) || os.length == 0; + } + + public static boolean isEmpty(Collection l) { + return isNull(l) || l.isEmpty(); + } + + public static boolean isEmpty(Map m) { + return isNull(m) || m.isEmpty(); + } + + public static boolean isNull(Object o) { + return o == null; + } +} diff --git a/src/com/litesuits/common/assist/FlashLight.java b/app/src/main/java/com/litesuits/common/assist/FlashLight.java similarity index 100% rename from src/com/litesuits/common/assist/FlashLight.java rename to app/src/main/java/com/litesuits/common/assist/FlashLight.java diff --git a/src/com/litesuits/common/assist/KeyguardLock.java b/app/src/main/java/com/litesuits/common/assist/KeyguardLock.java similarity index 100% rename from src/com/litesuits/common/assist/KeyguardLock.java rename to app/src/main/java/com/litesuits/common/assist/KeyguardLock.java diff --git a/src/com/litesuits/common/assist/Network.java b/app/src/main/java/com/litesuits/common/assist/Network.java similarity index 100% rename from src/com/litesuits/common/assist/Network.java rename to app/src/main/java/com/litesuits/common/assist/Network.java diff --git a/src/com/litesuits/common/assist/SilentInstaller.java b/app/src/main/java/com/litesuits/common/assist/SilentInstaller.java similarity index 100% rename from src/com/litesuits/common/assist/SilentInstaller.java rename to app/src/main/java/com/litesuits/common/assist/SilentInstaller.java diff --git a/src/com/litesuits/common/assist/TimeAverager.java b/app/src/main/java/com/litesuits/common/assist/TimeAverager.java similarity index 94% rename from src/com/litesuits/common/assist/TimeAverager.java rename to app/src/main/java/com/litesuits/common/assist/TimeAverager.java index f2d50b2..f5708e5 100755 --- a/src/com/litesuits/common/assist/TimeAverager.java +++ b/app/src/main/java/com/litesuits/common/assist/TimeAverager.java @@ -1,65 +1,65 @@ -package com.litesuits.common.assist; - - -/** - * 时间均值计算器,只能用于单线程计时。 - * - * @author MaTianyu - * 2013-12-12上午1:23:19 - */ -public class TimeAverager { - /** - * 计时器 - */ - private TimeCounter tc = new TimeCounter(); - /** - * 均值器 - */ - private Averager av = new Averager(); - - /** - * 一个计时开始 - */ - public long start() { - return tc.start(); - } - - /** - * 一个计时结束 - */ - public long end() { - long time = tc.duration(); - av.add(time); - return time; - } - - /** - * 一个计时结束,并且启动下次计时。 - */ - public long endAndRestart() { - long time = tc.durationRestart(); - av.add(time); - return time; - } - - /** - * 求全部计时均值 - */ - public Number average() { - return av.getAverage(); - } - - /** - * 打印全部时间值 - */ - public void print() { - av.print(); - } - - /** - * 清楚数据 - */ - public void clear() { - av.clear(); - } -} +package com.litesuits.common.assist; + + +/** + * 时间均值计算器,只能用于单线程计时。 + * + * @author MaTianyu + * 2013-12-12上午1:23:19 + */ +public class TimeAverager { + /** + * 计时器 + */ + private TimeCounter tc = new TimeCounter(); + /** + * 均值器 + */ + private Averager av = new Averager(); + + /** + * 一个计时开始 + */ + public long start() { + return tc.start(); + } + + /** + * 一个计时结束 + */ + public long end() { + long time = tc.duration(); + av.add(time); + return time; + } + + /** + * 一个计时结束,并且启动下次计时。 + */ + public long endAndRestart() { + long time = tc.durationRestart(); + av.add(time); + return time; + } + + /** + * 求全部计时均值 + */ + public Number average() { + return av.getAverage(); + } + + /** + * 打印全部时间值 + */ + public void print() { + av.print(); + } + + /** + * 清楚数据 + */ + public void clear() { + av.clear(); + } +} diff --git a/src/com/litesuits/common/assist/TimeCounter.java b/app/src/main/java/com/litesuits/common/assist/TimeCounter.java similarity index 94% rename from src/com/litesuits/common/assist/TimeCounter.java rename to app/src/main/java/com/litesuits/common/assist/TimeCounter.java index a93e274..dcd6d51 100755 --- a/src/com/litesuits/common/assist/TimeCounter.java +++ b/app/src/main/java/com/litesuits/common/assist/TimeCounter.java @@ -1,58 +1,58 @@ -package com.litesuits.common.assist; - -import com.litesuits.android.log.Log; - -/** - * Time Counter. - * - * @author MaTianyu - * 2013-12-11下午3:42:28 - */ -public class TimeCounter { - - private static final String TAG = TimeCounter.class.getSimpleName(); - private long t; - - public TimeCounter() { - start(); - } - - /** - * Count start. - */ - public long start() { - t = System.currentTimeMillis(); - return t; - } - - /** - * Get duration and restart. - */ - public long durationRestart() { - long now = System.currentTimeMillis(); - long d = now - t; - t = now; - return d; - } - - /** - * Get duration. - */ - public long duration() { - return System.currentTimeMillis() - t; - } - - /** - * Print duration. - */ - public void printDuration(String tag) { - Log.i(TAG, tag + " : " + duration()); - } - - /** - * Print duration. - */ - public void printDurationRestart(String tag) { - Log.i(TAG, tag + " : " + durationRestart()); - } +package com.litesuits.common.assist; + +import com.litesuits.android.log.Log; + +/** + * Time Counter. + * + * @author MaTianyu + * 2013-12-11下午3:42:28 + */ +public class TimeCounter { + + private static final String TAG = TimeCounter.class.getSimpleName(); + private long t; + + public TimeCounter() { + start(); + } + + /** + * Count start. + */ + public long start() { + t = System.currentTimeMillis(); + return t; + } + + /** + * Get duration and restart. + */ + public long durationRestart() { + long now = System.currentTimeMillis(); + long d = now - t; + t = now; + return d; + } + + /** + * Get duration. + */ + public long duration() { + return System.currentTimeMillis() - t; + } + + /** + * Print duration. + */ + public void printDuration(String tag) { + Log.i(TAG, tag + " : " + duration()); + } + + /** + * Print duration. + */ + public void printDurationRestart(String tag) { + Log.i(TAG, tag + " : " + durationRestart()); + } } \ No newline at end of file diff --git a/src/com/litesuits/common/assist/Toastor.java b/app/src/main/java/com/litesuits/common/assist/Toastor.java similarity index 100% rename from src/com/litesuits/common/assist/Toastor.java rename to app/src/main/java/com/litesuits/common/assist/Toastor.java diff --git a/src/com/litesuits/common/assist/WakeLock.java b/app/src/main/java/com/litesuits/common/assist/WakeLock.java similarity index 100% rename from src/com/litesuits/common/assist/WakeLock.java rename to app/src/main/java/com/litesuits/common/assist/WakeLock.java diff --git a/src/com/litesuits/common/data/DataKeeper.java b/app/src/main/java/com/litesuits/common/data/DataKeeper.java similarity index 100% rename from src/com/litesuits/common/data/DataKeeper.java rename to app/src/main/java/com/litesuits/common/data/DataKeeper.java diff --git a/src/com/litesuits/common/data/cipher/Base64Cipher.java b/app/src/main/java/com/litesuits/common/data/cipher/Base64Cipher.java similarity index 100% rename from src/com/litesuits/common/data/cipher/Base64Cipher.java rename to app/src/main/java/com/litesuits/common/data/cipher/Base64Cipher.java diff --git a/src/com/litesuits/common/data/cipher/Cipher.java b/app/src/main/java/com/litesuits/common/data/cipher/Cipher.java similarity index 100% rename from src/com/litesuits/common/data/cipher/Cipher.java rename to app/src/main/java/com/litesuits/common/data/cipher/Cipher.java diff --git a/src/com/litesuits/common/data/cipher/Decrypt.java b/app/src/main/java/com/litesuits/common/data/cipher/Decrypt.java similarity index 100% rename from src/com/litesuits/common/data/cipher/Decrypt.java rename to app/src/main/java/com/litesuits/common/data/cipher/Decrypt.java diff --git a/src/com/litesuits/common/data/cipher/Encrypt.java b/app/src/main/java/com/litesuits/common/data/cipher/Encrypt.java similarity index 100% rename from src/com/litesuits/common/data/cipher/Encrypt.java rename to app/src/main/java/com/litesuits/common/data/cipher/Encrypt.java diff --git a/src/com/litesuits/common/io/Charsets.java b/app/src/main/java/com/litesuits/common/io/Charsets.java similarity index 97% rename from src/com/litesuits/common/io/Charsets.java rename to app/src/main/java/com/litesuits/common/io/Charsets.java index 17dfd97..044df22 100644 --- a/src/com/litesuits/common/io/Charsets.java +++ b/app/src/main/java/com/litesuits/common/io/Charsets.java @@ -1,37 +1,37 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import java.nio.charset.Charset; - -/** - * Charsets - */ -public class Charsets { - public static Charset toCharset(Charset charset) { - return charset == null ? Charset.defaultCharset() : charset; - } - public static Charset toCharset(String charset) { - return charset == null ? Charset.defaultCharset() : Charset.forName(charset); - } - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - public static final Charset US_ASCII = Charset.forName("US-ASCII"); - public static final Charset UTF_16 = Charset.forName("UTF-16"); - public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); - public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); - public static final Charset UTF_8 = Charset.forName("UTF-8"); -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import java.nio.charset.Charset; + +/** + * Charsets + */ +public class Charsets { + public static Charset toCharset(Charset charset) { + return charset == null ? Charset.defaultCharset() : charset; + } + public static Charset toCharset(String charset) { + return charset == null ? Charset.defaultCharset() : Charset.forName(charset); + } + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + public static final Charset UTF_16 = Charset.forName("UTF-16"); + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + public static final Charset UTF_8 = Charset.forName("UTF-8"); +} diff --git a/src/com/litesuits/common/io/FileUtils.java b/app/src/main/java/com/litesuits/common/io/FileUtils.java similarity index 97% rename from src/com/litesuits/common/io/FileUtils.java rename to app/src/main/java/com/litesuits/common/io/FileUtils.java index c9a02ae..87ad6d3 100644 --- a/src/com/litesuits/common/io/FileUtils.java +++ b/app/src/main/java/com/litesuits/common/io/FileUtils.java @@ -1,2547 +1,2547 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import java.io.*; -import java.math.BigInteger; -import java.net.URL; -import java.net.URLConnection; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.util.*; - - -/** - * General file manipulation utilities. - *

- * Facilities are provided in the following areas: - *

    - *
  • writing to a file - *
  • reading from a file - *
  • make a directory including parent directories - *
  • copying files and directories - *
  • deleting files and directories - *
  • converting to and from a URL - *
  • listing files and directories by filter and extension - *
  • comparing file content - *
  • file last changed date - *
  • calculating a checksum - *
- *

- * Origin of code: Excalibur, Alexandria, Commons-Utils - * - * @version $Id: FileUtils.java 1349509 2012-06-12 20:39:23Z ggregory $ - */ -public class FileUtils { - - /** - * Instances should NOT be constructed in standard programming. - */ - public FileUtils() { - super(); - } - - /** - * The number of bytes in a kilobyte. - */ - public static final long ONE_KB = 1024; - - /** - * The number of bytes in a kilobyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB); - - /** - * The number of bytes in a megabyte. - */ - public static final long ONE_MB = ONE_KB * ONE_KB; - - /** - * The number of bytes in a megabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI); - - /** - * The file copy buffer size (30 MB) - */ - private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; - - /** - * The number of bytes in a gigabyte. - */ - public static final long ONE_GB = ONE_KB * ONE_MB; - - /** - * The number of bytes in a gigabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI); - - /** - * The number of bytes in a terabyte. - */ - public static final long ONE_TB = ONE_KB * ONE_GB; - - /** - * The number of bytes in a terabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI); - - /** - * The number of bytes in a petabyte. - */ - public static final long ONE_PB = ONE_KB * ONE_TB; - - /** - * The number of bytes in a petabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI); - - /** - * The number of bytes in an exabyte. - */ - public static final long ONE_EB = ONE_KB * ONE_PB; - - /** - * The number of bytes in an exabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI); - - /** - * The number of bytes in a zettabyte. - */ - public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB)); - - /** - * The number of bytes in a yottabyte. - */ - public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB); - - /** - * An empty array of type File. - */ - public static final File[] EMPTY_FILE_ARRAY = new File[0]; - - /** - * The UTF-8 character set, used to decode octets in URLs. - */ - private static final Charset UTF8 = Charset.forName("UTF-8"); - - //----------------------------------------------------------------------- - - /** - * Construct a file from the set of name elements. - * - * @param directory the parent directory - * @param names the name elements - * @return the file - * @since 2.1 - */ - public static File getFile(File directory, String... names) { - if (directory == null) { - throw new NullPointerException("directorydirectory must not be null"); - } - if (names == null) { - throw new NullPointerException("names must not be null"); - } - File file = directory; - for (String name : names) { - file = new File(file, name); - } - return file; - } - - /** - * Construct a file from the set of name elements. - * - * @param names the name elements - * @return the file - * @since 2.1 - */ - public static File getFile(String... names) { - if (names == null) { - throw new NullPointerException("names must not be null"); - } - File file = null; - for (String name : names) { - if (file == null) { - file = new File(name); - } else { - file = new File(file, name); - } - } - return file; - } - - /** - * Returns the path to the system temporary directory. - * - * @return the path to the system temporary directory. - * @since 2.0 - */ - public static String getTempDirectoryPath() { - return System.getProperty("java.io.tmpdir"); - } - - /** - * Returns a {@link java.io.File} representing the system temporary directory. - * - * @return the system temporary directory. - * @since 2.0 - */ - public static File getTempDirectory() { - return new File(getTempDirectoryPath()); - } - - /** - * Returns the path to the user's home directory. - * - * @return the path to the user's home directory. - * @since 2.0 - */ - public static String getUserDirectoryPath() { - return System.getProperty("user.home"); - } - - /** - * Returns a {@link java.io.File} representing the user's home directory. - * - * @return the user's home directory. - * @since 2.0 - */ - public static File getUserDirectory() { - return new File(getUserDirectoryPath()); - } - - //----------------------------------------------------------------------- - - /** - * Opens a {@link java.io.FileInputStream} for the specified file, providing better - * error messages than simply calling new FileInputStream(file). - *

- * At the end of the method either the stream will be successfully opened, - * or an exception will have been thrown. - *

- * An exception is thrown if the file does not exist. - * An exception is thrown if the file object exists but is a directory. - * An exception is thrown if the file exists but cannot be read. - * - * @param file the file to open for input, must not be {@code null} - * @return a new {@link java.io.FileInputStream} for the specified file - * @throws java.io.FileNotFoundException if the file does not exist - * @throws java.io.IOException if the file object is a directory - * @throws java.io.IOException if the file cannot be read - * @since 1.3 - */ - public static FileInputStream openInputStream(File file) throws IOException { - if (file.exists()) { - if (file.isDirectory()) { - throw new IOException("File '" + file + "' exists but is a directory"); - } - if (file.canRead() == false) { - throw new IOException("File '" + file + "' cannot be read"); - } - } else { - throw new FileNotFoundException("File '" + file + "' does not exist"); - } - return new FileInputStream(file); - } - - //----------------------------------------------------------------------- - - /** - * Opens a {@link java.io.FileOutputStream} for the specified file, checking and - * creating the parent directory if it does not exist. - *

- * At the end of the method either the stream will be successfully opened, - * or an exception will have been thrown. - *

- * The parent directory will be created if it does not exist. - * The file will be created if it does not exist. - * An exception is thrown if the file object exists but is a directory. - * An exception is thrown if the file exists but cannot be written to. - * An exception is thrown if the parent directory cannot be created. - * - * @param file the file to open for output, must not be {@code null} - * @return a new {@link java.io.FileOutputStream} for the specified file - * @throws java.io.IOException if the file object is a directory - * @throws java.io.IOException if the file cannot be written to - * @throws java.io.IOException if a parent directory needs creating but that fails - * @since 1.3 - */ - public static FileOutputStream openOutputStream(File file) throws IOException { - return openOutputStream(file, false); - } - - /** - * Opens a {@link java.io.FileOutputStream} for the specified file, checking and - * creating the parent directory if it does not exist. - *

- * At the end of the method either the stream will be successfully opened, - * or an exception will have been thrown. - *

- * The parent directory will be created if it does not exist. - * The file will be created if it does not exist. - * An exception is thrown if the file object exists but is a directory. - * An exception is thrown if the file exists but cannot be written to. - * An exception is thrown if the parent directory cannot be created. - * - * @param file the file to open for output, must not be {@code null} - * @param append if {@code true}, then bytes will be added to the - * end of the file rather than overwriting - * @return a new {@link java.io.FileOutputStream} for the specified file - * @throws java.io.IOException if the file object is a directory - * @throws java.io.IOException if the file cannot be written to - * @throws java.io.IOException if a parent directory needs creating but that fails - * @since 2.1 - */ - public static FileOutputStream openOutputStream(File file, boolean append) throws IOException { - if (file.exists()) { - if (file.isDirectory()) { - throw new IOException("File '" + file + "' exists but is a directory"); - } - if (file.canWrite() == false) { - throw new IOException("File '" + file + "' cannot be written to"); - } - } else { - File parent = file.getParentFile(); - if (parent != null) { - if (!parent.mkdirs() && !parent.isDirectory()) { - throw new IOException("Directory '" + parent + "' could not be created"); - } - } - } - return new FileOutputStream(file, append); - } - - //----------------------------------------------------------------------- - - /** - * Returns a human-readable version of the file size, where the input represents a specific number of bytes. - *

- * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the - * nearest GB boundary. - *

- *

- * Similarly for the 1MB and 1KB boundaries. - *

- * - * @param size the number of bytes - * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) - * @see IO-226 - should the rounding be changed? - * @since 2.4 - */ - // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? - public static String byteCountToDisplaySize(BigInteger size) { - String displaySize; - - if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_EB_BI)) + " EB"; - } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_PB_BI)) + " PB"; - } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_TB_BI)) + " TB"; - } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_GB_BI)) + " GB"; - } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_MB_BI)) + " MB"; - } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_KB_BI)) + " KB"; - } else { - displaySize = String.valueOf(size) + " bytes"; - } - return displaySize; - } - - /** - * Returns a human-readable version of the file size, where the input represents a specific number of bytes. - *

- * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the - * nearest GB boundary. - *

- *

- * Similarly for the 1MB and 1KB boundaries. - *

- * - * @param size the number of bytes - * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) - * @see IO-226 - should the rounding be changed? - */ - // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? - public static String byteCountToDisplaySize(long size) { - return byteCountToDisplaySize(BigInteger.valueOf(size)); - } - - //----------------------------------------------------------------------- - - /** - * Implements the same behaviour as the "touch" utility on Unix. It creates - * a new file with size 0 or, if the file exists already, it is opened and - * closed without modifying it, but updating the file date and time. - *

- * NOTE: As from v1.3, this method throws an IOException if the last - * modified date of the file cannot be set. Also, as from v1.3 this method - * creates parent directories if they do not exist. - * - * @param file the File to touch - * @throws java.io.IOException If an I/O problem occurs - */ - public static void touch(File file) throws IOException { - if (!file.exists()) { - OutputStream out = openOutputStream(file); - IOUtils.closeQuietly(out); - } - boolean success = file.setLastModified(System.currentTimeMillis()); - if (!success) { - throw new IOException("Unable to set the last modification time for " + file); - } - } - - //----------------------------------------------------------------------- - - /** - * Converts a Collection containing java.io.File instanced into array - * representation. This is to account for the difference between - * File.listFiles() and FileUtils.listFiles(). - * - * @param files a Collection containing java.io.File instances - * @return an array of java.io.File - */ - public static File[] convertFileCollectionToFileArray(Collection files) { - return files.toArray(new File[files.size()]); - } - - //----------------------------------------------------------------------- - - /** - * Converts an array of file extensions to suffixes for use - * with IOFileFilters. - * - * @param extensions an array of extensions. Format: {"java", "xml"} - * @return an array of suffixes. Format: {".java", ".xml"} - */ - private static String[] toSuffixes(String[] extensions) { - String[] suffixes = new String[extensions.length]; - for (int i = 0; i < extensions.length; i++) { - suffixes[i] = "." + extensions[i]; - } - return suffixes; - } - - //----------------------------------------------------------------------- - - /** - * Compares the contents of two files to determine if they are equal or not. - *

- * This method checks to see if the two files are different lengths - * or if they point to the same file, before resorting to byte-by-byte - * comparison of the contents. - *

- * Code origin: Avalon - * - * @param file1 the first file - * @param file2 the second file - * @return true if the content of the files are equal or they both don't - * exist, false otherwise - * @throws java.io.IOException in case of an I/O error - */ - public static boolean contentEquals(File file1, File file2) throws IOException { - boolean file1Exists = file1.exists(); - if (file1Exists != file2.exists()) { - return false; - } - - if (!file1Exists) { - // two not existing files are equal - return true; - } - - if (file1.isDirectory() || file2.isDirectory()) { - // don't want to compare directory contents - throw new IOException("Can't compare directories, only files"); - } - - if (file1.length() != file2.length()) { - // lengths differ, cannot be equal - return false; - } - - if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { - // same file - return true; - } - - InputStream input1 = null; - InputStream input2 = null; - try { - input1 = new FileInputStream(file1); - input2 = new FileInputStream(file2); - return IOUtils.contentEquals(input1, input2); - - } finally { - IOUtils.closeQuietly(input1); - IOUtils.closeQuietly(input2); - } - } - - //----------------------------------------------------------------------- - - /** - * Compares the contents of two files to determine if they are equal or not. - *

- * This method checks to see if the two files point to the same file, - * before resorting to line-by-line comparison of the contents. - *

- * - * @param file1 the first file - * @param file2 the second file - * @param charsetName the character encoding to be used. - * May be null, in which case the platform default is used - * @return true if the content of the files are equal or neither exists, - * false otherwise - * @throws java.io.IOException in case of an I/O error - * @see IOUtils#contentEqualsIgnoreEOL(java.io.Reader, java.io.Reader) - * @since 2.2 - */ - public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException { - boolean file1Exists = file1.exists(); - if (file1Exists != file2.exists()) { - return false; - } - - if (!file1Exists) { - // two not existing files are equal - return true; - } - - if (file1.isDirectory() || file2.isDirectory()) { - // don't want to compare directory contents - throw new IOException("Can't compare directories, only files"); - } - - if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { - // same file - return true; - } - - Reader input1 = null; - Reader input2 = null; - try { - if (charsetName == null) { - input1 = new InputStreamReader(new FileInputStream(file1)); - input2 = new InputStreamReader(new FileInputStream(file2)); - } else { - input1 = new InputStreamReader(new FileInputStream(file1), charsetName); - input2 = new InputStreamReader(new FileInputStream(file2), charsetName); - } - return IOUtils.contentEqualsIgnoreEOL(input1, input2); - - } finally { - IOUtils.closeQuietly(input1); - IOUtils.closeQuietly(input2); - } - } - - //----------------------------------------------------------------------- - - /** - * Convert from a URL to a File. - *

- * From version 1.1 this method will decode the URL. - * Syntax such as file:///my%20docs/file.txt will be - * correctly decoded to /my docs/file.txt. Starting with version - * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters. - * Additionally, malformed percent-encoded octets are handled leniently by - * passing them through literally. - * - * @param url the file URL to convert, {@code null} returns {@code null} - * @return the equivalent File object, or {@code null} - * if the URL's protocol is not file - */ - public static File toFile(URL url) { - if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) { - return null; - } else { - String filename = url.getFile().replace('/', File.separatorChar); - filename = decodeUrl(filename); - return new File(filename); - } - } - - /** - * Decodes the specified URL as per RFC 3986, i.e. transforms - * percent-encoded octets to characters by decoding with the UTF-8 character - * set. This function is primarily intended for usage with - * {@link java.net.URL} which unfortunately does not enforce proper URLs. As - * such, this method will leniently accept invalid characters or malformed - * percent-encoded octets and simply pass them literally through to the - * result string. Except for rare edge cases, this will make unencoded URLs - * pass through unaltered. - * - * @param url The URL to decode, may be {@code null}. - * @return The decoded URL or {@code null} if the input was - * {@code null}. - */ - static String decodeUrl(String url) { - String decoded = url; - if (url != null && url.indexOf('%') >= 0) { - int n = url.length(); - StringBuffer buffer = new StringBuffer(); - ByteBuffer bytes = ByteBuffer.allocate(n); - for (int i = 0; i < n; ) { - if (url.charAt(i) == '%') { - try { - do { - byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); - bytes.put(octet); - i += 3; - } while (i < n && url.charAt(i) == '%'); - continue; - } catch (RuntimeException e) { - // malformed percent-encoded octet, fall through and - // append characters literally - } finally { - if (bytes.position() > 0) { - bytes.flip(); - buffer.append(UTF8.decode(bytes).toString()); - bytes.clear(); - } - } - } - buffer.append(url.charAt(i++)); - } - decoded = buffer.toString(); - } - return decoded; - } - - /** - * Converts each of an array of URL to a File. - *

- * Returns an array of the same size as the input. - * If the input is {@code null}, an empty array is returned. - * If the input contains {@code null}, the output array contains {@code null} at the same - * index. - *

- * This method will decode the URL. - * Syntax such as file:///my%20docs/file.txt will be - * correctly decoded to /my docs/file.txt. - * - * @param urls the file URLs to convert, {@code null} returns empty array - * @return a non-{@code null} array of Files matching the input, with a {@code null} item - * if there was a {@code null} at that index in the input array - * @throws IllegalArgumentException if any file is not a URL file - * @throws IllegalArgumentException if any file is incorrectly encoded - * @since 1.1 - */ - public static File[] toFiles(URL[] urls) { - if (urls == null || urls.length == 0) { - return EMPTY_FILE_ARRAY; - } - File[] files = new File[urls.length]; - for (int i = 0; i < urls.length; i++) { - URL url = urls[i]; - if (url != null) { - if (url.getProtocol().equals("file") == false) { - throw new IllegalArgumentException( - "URL could not be converted to a File: " + url); - } - files[i] = toFile(url); - } - } - return files; - } - - /** - * Converts each of an array of File to a URL. - *

- * Returns an array of the same size as the input. - * - * @param files the files to convert, must not be {@code null} - * @return an array of URLs matching the input - * @throws java.io.IOException if a file cannot be converted - * @throws NullPointerException if the parameter is null - */ - public static URL[] toURLs(File[] files) throws IOException { - URL[] urls = new URL[files.length]; - - for (int i = 0; i < urls.length; i++) { - urls[i] = files[i].toURI().toURL(); - } - - return urls; - } - - //----------------------------------------------------------------------- - - /** - * Copies a file to a directory preserving the file date. - *

- * This method copies the contents of the specified source file - * to a file of the same name in the specified destination directory. - * The destination directory is created if it does not exist. - * If the destination file exists, then this method will overwrite it. - *

- * Note: This method tries to preserve the file's last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destDir the directory to place the copy in, must not be {@code null} - * @throws NullPointerException if source or destination is null - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFile(java.io.File, java.io.File, boolean) - */ - public static void copyFileToDirectory(File srcFile, File destDir) throws IOException { - copyFileToDirectory(srcFile, destDir, true); - } - - /** - * Copies a file to a directory optionally preserving the file date. - *

- * This method copies the contents of the specified source file - * to a file of the same name in the specified destination directory. - * The destination directory is created if it does not exist. - * If the destination file exists, then this method will overwrite it. - *

- * Note: Setting preserveFileDate to - * {@code true} tries to preserve the file's last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destDir the directory to place the copy in, must not be {@code null} - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFile(java.io.File, java.io.File, boolean) - * @since 1.3 - */ - public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException { - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (destDir.exists() && destDir.isDirectory() == false) { - throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); - } - File destFile = new File(destDir, srcFile.getName()); - copyFile(srcFile, destFile, preserveFileDate); - } - - /** - * Copies a file to a new location preserving the file date. - *

- * This method copies the contents of the specified source file to the - * specified destination file. The directory holding the destination file is - * created if it does not exist. If the destination file exists, then this - * method will overwrite it. - *

- * Note: This method tries to preserve the file's last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destFile the new file, must not be {@code null} - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFileToDirectory(java.io.File, java.io.File) - */ - public static void copyFile(File srcFile, File destFile) throws IOException { - copyFile(srcFile, destFile, true); - } - - /** - * Copies a file to a new location. - *

- * This method copies the contents of the specified source file - * to the specified destination file. - * The directory holding the destination file is created if it does not exist. - * If the destination file exists, then this method will overwrite it. - *

- * Note: Setting preserveFileDate to - * {@code true} tries to preserve the file's last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destFile the new file, must not be {@code null} - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFileToDirectory(java.io.File, java.io.File, boolean) - */ - public static void copyFile(File srcFile, File destFile, - boolean preserveFileDate) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destFile == null) { - throw new NullPointerException("Destination must not be null"); - } - if (srcFile.exists() == false) { - throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); - } - if (srcFile.isDirectory()) { - throw new IOException("Source '" + srcFile + "' exists but is a directory"); - } - if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { - throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); - } - File parentFile = destFile.getParentFile(); - if (parentFile != null) { - if (!parentFile.mkdirs() && !parentFile.isDirectory()) { - throw new IOException("Destination '" + parentFile + "' directory cannot be created"); - } - } - if (destFile.exists() && destFile.canWrite() == false) { - throw new IOException("Destination '" + destFile + "' exists but is read-only"); - } - doCopyFile(srcFile, destFile, preserveFileDate); - } - - /** - * Copy bytes from a File to an OutputStream. - *

- * This method buffers the input internally, so there is no need to use a BufferedInputStream. - *

- * - * @param input the File to read from - * @param output the OutputStream to write to - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.1 - */ - public static long copyFile(File input, OutputStream output) throws IOException { - final FileInputStream fis = new FileInputStream(input); - try { - return IOUtils.copyLarge(fis, output); - } finally { - fis.close(); - } - } - - /** - * Internal copy file method. - * - * @param srcFile the validated source file, must not be {@code null} - * @param destFile the validated destination file, must not be {@code null} - * @param preserveFileDate whether to preserve the file date - * @throws java.io.IOException if an error occurs - */ - private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { - if (destFile.exists() && destFile.isDirectory()) { - throw new IOException("Destination '" + destFile + "' exists but is a directory"); - } - - FileInputStream fis = null; - FileOutputStream fos = null; - FileChannel input = null; - FileChannel output = null; - try { - fis = new FileInputStream(srcFile); - fos = new FileOutputStream(destFile); - input = fis.getChannel(); - output = fos.getChannel(); - long size = input.size(); - long pos = 0; - long count = 0; - while (pos < size) { - count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos; - pos += output.transferFrom(input, pos, count); - } - } finally { - IOUtils.closeQuietly(output); - IOUtils.closeQuietly(fos); - IOUtils.closeQuietly(input); - IOUtils.closeQuietly(fis); - } - - if (srcFile.length() != destFile.length()) { - throw new IOException("Failed to copy full contents from '" + - srcFile + "' to '" + destFile + "'"); - } - if (preserveFileDate) { - destFile.setLastModified(srcFile.lastModified()); - } - } - - //----------------------------------------------------------------------- - - /** - * Copies a directory to within another directory preserving the file dates. - *

- * This method copies the source directory and all its contents to a - * directory of the same name in the specified destination directory. - *

- * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

- * Note: This method tries to preserve the files' last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the directory to place the copy in, must not be {@code null} - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.2 - */ - public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException { - if (srcDir == null) { - throw new NullPointerException("Source must not be null"); - } - if (srcDir.exists() && srcDir.isDirectory() == false) { - throw new IllegalArgumentException("Source '" + destDir + "' is not a directory"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (destDir.exists() && destDir.isDirectory() == false) { - throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); - } - copyDirectory(srcDir, new File(destDir, srcDir.getName()), true); - } - - /** - * Copies a whole directory to a new location preserving the file dates. - *

- * This method copies the specified directory and all its child - * directories and files to the specified destination. - * The destination is the new location and name of the directory. - *

- * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

- * Note: This method tries to preserve the files' last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.1 - */ - public static void copyDirectory(File srcDir, File destDir) throws IOException { - copyDirectory(srcDir, destDir, true); - } - - /** - * Copies a whole directory to a new location. - *

- * This method copies the contents of the specified source directory - * to within the specified destination directory. - *

- * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

- * Note: Setting preserveFileDate to - * {@code true} tries to preserve the files' last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.1 - */ - public static void copyDirectory(File srcDir, File destDir, - boolean preserveFileDate) throws IOException { - copyDirectory(srcDir, destDir, null, preserveFileDate); - } - - /** - * Copies a filtered directory to a new location preserving the file dates. - *

- * This method copies the contents of the specified source directory - * to within the specified destination directory. - *

- * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

- * Note: This method tries to preserve the files' last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - *

- *

Example: Copy directories only

- *
-     *  // only copy the directory structure
-     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
-     *  
- * - *

Example: Copy directories and txt files

- *
-     *  // Create a filter for ".txt" files
-     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
-     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
-     *
-     *  // Create a filter for either directories or ".txt" files
-     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
-     *
-     *  // Copy using the filter
-     *  FileUtils.copyDirectory(srcDir, destDir, filter);
-     *  
- * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @param filter the filter to apply, null means copy all directories and files - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.4 - */ - public static void copyDirectory(File srcDir, File destDir, - FileFilter filter) throws IOException { - copyDirectory(srcDir, destDir, filter, true); - } - - /** - * Copies a filtered directory to a new location. - *

- * This method copies the contents of the specified source directory - * to within the specified destination directory. - *

- * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

- * Note: Setting preserveFileDate to - * {@code true} tries to preserve the files' last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - *

- *

Example: Copy directories only

- *
-     *  // only copy the directory structure
-     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
-     *  
- * - *

Example: Copy directories and txt files

- *
-     *  // Create a filter for ".txt" files
-     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
-     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
-     *
-     *  // Create a filter for either directories or ".txt" files
-     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
-     *
-     *  // Copy using the filter
-     *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
-     *  
- * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @param filter the filter to apply, null means copy all directories and files - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.4 - */ - public static void copyDirectory(File srcDir, File destDir, - FileFilter filter, boolean preserveFileDate) throws IOException { - if (srcDir == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (srcDir.exists() == false) { - throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); - } - if (srcDir.isDirectory() == false) { - throw new IOException("Source '" + srcDir + "' exists but is not a directory"); - } - if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) { - throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); - } - - // Cater for destination being directory within the source directory (see IO-141) - List exclusionList = null; - if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { - File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); - if (srcFiles != null && srcFiles.length > 0) { - exclusionList = new ArrayList(srcFiles.length); - for (File srcFile : srcFiles) { - File copiedFile = new File(destDir, srcFile.getName()); - exclusionList.add(copiedFile.getCanonicalPath()); - } - } - } - doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList); - } - - /** - * Internal copy directory method. - * - * @param srcDir the validated source directory, must not be {@code null} - * @param destDir the validated destination directory, must not be {@code null} - * @param filter the filter to apply, null means copy all directories and files - * @param preserveFileDate whether to preserve the file date - * @param exclusionList List of files and directories to exclude from the copy, may be null - * @throws java.io.IOException if an error occurs - * @since 1.1 - */ - private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter, - boolean preserveFileDate, List exclusionList) throws IOException { - // recurse - File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); - if (srcFiles == null) { // null if abstract pathname does not denote a directory, or if an I/O error occurs - throw new IOException("Failed to list contents of " + srcDir); - } - if (destDir.exists()) { - if (destDir.isDirectory() == false) { - throw new IOException("Destination '" + destDir + "' exists but is not a directory"); - } - } else { - if (!destDir.mkdirs() && !destDir.isDirectory()) { - throw new IOException("Destination '" + destDir + "' directory cannot be created"); - } - } - if (destDir.canWrite() == false) { - throw new IOException("Destination '" + destDir + "' cannot be written to"); - } - for (File srcFile : srcFiles) { - File dstFile = new File(destDir, srcFile.getName()); - if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) { - if (srcFile.isDirectory()) { - doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList); - } else { - doCopyFile(srcFile, dstFile, preserveFileDate); - } - } - } - - // Do this last, as the above has probably affected directory metadata - if (preserveFileDate) { - destDir.setLastModified(srcDir.lastModified()); - } - } - - //----------------------------------------------------------------------- - - /** - * Copies bytes from the URL source to a file - * destination. The directories up to destination - * will be created if they don't already exist. destination - * will be overwritten if it already exists. - *

- * Warning: this method does not set a connection or read timeout and thus - * might block forever. Use {@link #copyURLToFile(java.net.URL, java.io.File, int, int)} - * with reasonable timeouts to prevent this. - * - * @param source the URL to copy bytes from, must not be {@code null} - * @param destination the non-directory File to write bytes to - * (possibly overwriting), must not be {@code null} - * @throws java.io.IOException if source URL cannot be opened - * @throws java.io.IOException if destination is a directory - * @throws java.io.IOException if destination cannot be written - * @throws java.io.IOException if destination needs creating but can't be - * @throws java.io.IOException if an IO error occurs during copying - */ - public static void copyURLToFile(URL source, File destination) throws IOException { - InputStream input = source.openStream(); - copyInputStreamToFile(input, destination); - } - - /** - * Copies bytes from the URL source to a file - * destination. The directories up to destination - * will be created if they don't already exist. destination - * will be overwritten if it already exists. - * - * @param source the URL to copy bytes from, must not be {@code null} - * @param destination the non-directory File to write bytes to - * (possibly overwriting), must not be {@code null} - * @param connectionTimeout the number of milliseconds until this method - * will timeout if no connection could be established to the source - * @param readTimeout the number of milliseconds until this method will - * timeout if no data could be read from the source - * @throws java.io.IOException if source URL cannot be opened - * @throws java.io.IOException if destination is a directory - * @throws java.io.IOException if destination cannot be written - * @throws java.io.IOException if destination needs creating but can't be - * @throws java.io.IOException if an IO error occurs during copying - * @since 2.0 - */ - public static void copyURLToFile(URL source, File destination, - int connectionTimeout, int readTimeout) throws IOException { - URLConnection connection = source.openConnection(); - connection.setConnectTimeout(connectionTimeout); - connection.setReadTimeout(readTimeout); - InputStream input = connection.getInputStream(); - copyInputStreamToFile(input, destination); - } - - /** - * Copies bytes from an {@link java.io.InputStream} source to a file - * destination. The directories up to destination - * will be created if they don't already exist. destination - * will be overwritten if it already exists. - * - * @param source the InputStream to copy bytes from, must not be {@code null} - * @param destination the non-directory File to write bytes to - * (possibly overwriting), must not be {@code null} - * @throws java.io.IOException if destination is a directory - * @throws java.io.IOException if destination cannot be written - * @throws java.io.IOException if destination needs creating but can't be - * @throws java.io.IOException if an IO error occurs during copying - * @since 2.0 - */ - public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { - try { - FileOutputStream output = openOutputStream(destination); - try { - IOUtils.copy(source, output); - output.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(output); - } - } finally { - IOUtils.closeQuietly(source); - } - } - - //----------------------------------------------------------------------- - - /** - * Deletes a directory recursively. - * - * @param directory directory to delete - * @throws java.io.IOException in case deletion is unsuccessful - */ - public static void deleteDirectory(File directory) throws IOException { - if (!directory.exists()) { - return; - } - - if (!isSymlink(directory)) { - cleanDirectory(directory); - } - - if (!directory.delete()) { - String message = - "Unable to delete directory " + directory + "."; - throw new IOException(message); - } - } - - /** - * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. - *

- * The difference between File.delete() and this method are: - *

    - *
  • A directory to be deleted does not have to be empty.
  • - *
  • No exceptions are thrown when a file or directory cannot be deleted.
  • - *
- * - * @param file file or directory to delete, can be {@code null} - * @return {@code true} if the file or directory was deleted, otherwise - * {@code false} - * @since 1.4 - */ - public static boolean deleteQuietly(File file) { - if (file == null) { - return false; - } - try { - if (file.isDirectory()) { - cleanDirectory(file); - } - } catch (Exception ignored) { - } - - try { - return file.delete(); - } catch (Exception ignored) { - return false; - } - } - - /** - * Cleans a directory without deleting it. - * - * @param directory directory to clean - * @throws java.io.IOException in case cleaning is unsuccessful - */ - public static void cleanDirectory(File directory) throws IOException { - if (!directory.exists()) { - String message = directory + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (!directory.isDirectory()) { - String message = directory + " is not a directory"; - throw new IllegalArgumentException(message); - } - - File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - throw new IOException("Failed to list contents of " + directory); - } - - IOException exception = null; - for (File file : files) { - try { - forceDelete(file); - } catch (IOException ioe) { - exception = ioe; - } - } - - if (null != exception) { - throw exception; - } - } - - //----------------------------------------------------------------------- - - /** - * Waits for NFS to propagate a file creation, imposing a timeout. - *

- * This method repeatedly tests {@link java.io.File#exists()} until it returns - * true up to the maximum time specified in seconds. - * - * @param file the file to check, must not be {@code null} - * @param seconds the maximum time in seconds to wait - * @return true if file exists - * @throws NullPointerException if the file is {@code null} - */ - public static boolean waitFor(File file, int seconds) { - int timeout = 0; - int tick = 0; - while (!file.exists()) { - if (tick++ >= 10) { - tick = 0; - if (timeout++ > seconds) { - return false; - } - } - try { - Thread.sleep(100); - } catch (InterruptedException ignore) { - // ignore exception - } catch (Exception ex) { - break; - } - } - return true; - } - - //----------------------------------------------------------------------- - - /** - * Reads the contents of a file into a String. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static String readFileToString(File file, Charset encoding) throws IOException { - InputStream in = null; - try { - in = openInputStream(file); - return IOUtils.toString(in, Charsets.toCharset(encoding)); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Reads the contents of a file into a String. The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.3 - */ - public static String readFileToString(File file, String encoding) throws IOException { - return readFileToString(file, Charsets.toCharset(encoding)); - } - - - /** - * Reads the contents of a file into a String using the default encoding for the VM. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 1.3.1 - */ - public static String readFileToString(File file) throws IOException { - return readFileToString(file, Charset.defaultCharset()); - } - - /** - * Reads the contents of a file into a byte array. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 1.1 - */ - public static byte[] readFileToByteArray(File file) throws IOException { - InputStream in = null; - try { - in = openInputStream(file); - return IOUtils.toByteArray(in, file.length()); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Reads the contents of a file line by line to a List of Strings. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the list of Strings representing each line in the file, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static List readLines(File file, Charset encoding) throws IOException { - InputStream in = null; - try { - in = openInputStream(file); - return IOUtils.readLines(in, Charsets.toCharset(encoding)); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Reads the contents of a file line by line to a List of Strings. The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the list of Strings representing each line in the file, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static List readLines(File file, String encoding) throws IOException { - return readLines(file, Charsets.toCharset(encoding)); - } - - /** - * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @return the list of Strings representing each line in the file, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 1.3 - */ - public static List readLines(File file) throws IOException { - return readLines(file, Charset.defaultCharset()); - } - - //----------------------------------------------------------------------- - - /** - * Writes a String to a file creating the file if it does not exist. - *

- * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.4 - */ - public static void writeStringToFile(File file, String data, Charset encoding) throws IOException { - writeStringToFile(file, data, encoding, false); - } - - /** - * Writes a String to a file creating the file if it does not exist. - *

- * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - */ - public static void writeStringToFile(File file, String data, String encoding) throws IOException { - writeStringToFile(file, data, encoding, false); - } - - /** - * Writes a String to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException { - OutputStream out = null; - try { - out = openOutputStream(file, append); - IOUtils.write(data, out, encoding); - out.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Writes a String to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported by the VM - * @since 2.1 - */ - public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException { - writeStringToFile(file, data, Charsets.toCharset(encoding), append); - } - - /** - * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @throws java.io.IOException in case of an I/O error - */ - public static void writeStringToFile(File file, String data) throws IOException { - writeStringToFile(file, data, Charset.defaultCharset(), false); - } - - /** - * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void writeStringToFile(File file, String data, boolean append) throws IOException { - writeStringToFile(file, data, Charset.defaultCharset(), append); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @throws java.io.IOException in case of an I/O error - * @since 2.0 - */ - public static void write(File file, CharSequence data) throws IOException { - write(file, data, Charset.defaultCharset(), false); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void write(File file, CharSequence data, boolean append) throws IOException { - write(file, data, Charset.defaultCharset(), append); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static void write(File file, CharSequence data, Charset encoding) throws IOException { - write(file, data, encoding, false); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.0 - */ - public static void write(File file, CharSequence data, String encoding) throws IOException { - write(file, data, encoding, false); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static void write(File file, CharSequence data, Charset encoding, boolean append) throws IOException { - String str = data == null ? null : data.toString(); - writeStringToFile(file, str, encoding, append); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported by the VM - * @since IO 2.1 - */ - public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException { - write(file, data, Charsets.toCharset(encoding), append); - } - - /** - * Writes a byte array to a file creating the file if it does not exist. - *

- * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write to - * @param data the content to write to the file - * @throws java.io.IOException in case of an I/O error - * @since 1.1 - */ - public static void writeByteArrayToFile(File file, byte[] data) throws IOException { - writeByteArrayToFile(file, data, false); - } - - /** - * Writes a byte array to a file creating the file if it does not exist. - * - * @param file the file to write to - * @param data the content to write to the file - * @param append if {@code true}, then bytes will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since IO 2.1 - */ - public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException { - OutputStream out = null; - try { - out = openOutputStream(file, append); - out.write(data); - out.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The specified character encoding and the default line ending will be used. - *

- * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 1.1 - */ - public static void writeLines(File file, String encoding, Collection lines) throws IOException { - writeLines(file, encoding, lines, null, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line, optionally appending. - * The specified character encoding and the default line ending will be used. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.1 - */ - public static void writeLines(File file, String encoding, Collection lines, boolean append) throws IOException { - writeLines(file, encoding, lines, null, append); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the default line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @throws java.io.IOException in case of an I/O error - * @since 1.3 - */ - public static void writeLines(File file, Collection lines) throws IOException { - writeLines(file, null, lines, null, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the default line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void writeLines(File file, Collection lines, boolean append) throws IOException { - writeLines(file, null, lines, null, append); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The specified character encoding and the line ending will be used. - *

- * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 1.1 - */ - public static void writeLines(File file, String encoding, Collection lines, String lineEnding) - throws IOException { - writeLines(file, encoding, lines, lineEnding, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The specified character encoding and the line ending will be used. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.1 - */ - public static void writeLines(File file, String encoding, Collection lines, String lineEnding, boolean append) - throws IOException { - FileOutputStream out = null; - try { - out = openOutputStream(file, append); - final BufferedOutputStream buffer = new BufferedOutputStream(out); - IOUtils.writeLines(lines, lineEnding, buffer, encoding); - buffer.flush(); - out.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the specified line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @throws java.io.IOException in case of an I/O error - * @since 1.3 - */ - public static void writeLines(File file, Collection lines, String lineEnding) throws IOException { - writeLines(file, null, lines, lineEnding, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the specified line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void writeLines(File file, Collection lines, String lineEnding, boolean append) - throws IOException { - writeLines(file, null, lines, lineEnding, append); - } - - //----------------------------------------------------------------------- - - /** - * Deletes a file. If file is a directory, delete it and all sub-directories. - *

- * The difference between File.delete() and this method are: - *

    - *
  • A directory to be deleted does not have to be empty.
  • - *
  • You get exceptions when a file or directory cannot be deleted. - * (java.io.File methods returns a boolean)
  • - *
- * - * @param file file or directory to delete, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.FileNotFoundException if the file was not found - * @throws java.io.IOException in case deletion is unsuccessful - */ - public static void forceDelete(File file) throws IOException { - if (file.isDirectory()) { - deleteDirectory(file); - } else { - boolean filePresent = file.exists(); - if (!file.delete()) { - if (!filePresent) { - throw new FileNotFoundException("File does not exist: " + file); - } - String message = - "Unable to delete file: " + file; - throw new IOException(message); - } - } - } - - /** - * Schedules a file to be deleted when JVM exits. - * If file is directory delete it and all sub-directories. - * - * @param file file or directory to delete, must not be {@code null} - * @throws NullPointerException if the file is {@code null} - * @throws java.io.IOException in case deletion is unsuccessful - */ - public static void forceDeleteOnExit(File file) throws IOException { - if (file.isDirectory()) { - deleteDirectoryOnExit(file); - } else { - file.deleteOnExit(); - } - } - - /** - * Schedules a directory recursively for deletion on JVM exit. - * - * @param directory directory to delete, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.IOException in case deletion is unsuccessful - */ - private static void deleteDirectoryOnExit(File directory) throws IOException { - if (!directory.exists()) { - return; - } - - directory.deleteOnExit(); - if (!isSymlink(directory)) { - cleanDirectoryOnExit(directory); - } - } - - /** - * Cleans a directory without deleting it. - * - * @param directory directory to clean, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.IOException in case cleaning is unsuccessful - */ - private static void cleanDirectoryOnExit(File directory) throws IOException { - if (!directory.exists()) { - String message = directory + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (!directory.isDirectory()) { - String message = directory + " is not a directory"; - throw new IllegalArgumentException(message); - } - - File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - throw new IOException("Failed to list contents of " + directory); - } - - IOException exception = null; - for (File file : files) { - try { - forceDeleteOnExit(file); - } catch (IOException ioe) { - exception = ioe; - } - } - - if (null != exception) { - throw exception; - } - } - - /** - * Makes a directory, including any necessary but nonexistent parent - * directories. If a file already exists with specified name but it is - * not a directory then an IOException is thrown. - * If the directory cannot be created (or does not already exist) - * then an IOException is thrown. - * - * @param directory directory to create, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.IOException if the directory cannot be created or the file already exists but is not a directory - */ - public static void forceMkdir(File directory) throws IOException { - if (directory.exists()) { - if (!directory.isDirectory()) { - String message = - "File " - + directory - + " exists and is " - + "not a directory. Unable to create directory."; - throw new IOException(message); - } - } else { - if (!directory.mkdirs()) { - // Double-check that some other thread or process hasn't made - // the directory in the background - if (!directory.isDirectory()) { - String message = - "Unable to create directory " + directory; - throw new IOException(message); - } - } - } - } - - //----------------------------------------------------------------------- - - /** - * Returns the size of the specified file or directory. If the provided - * {@link java.io.File} is a regular file, then the file's length is returned. - * If the argument is a directory, then the size of the directory is - * calculated recursively. If a directory or subdirectory is security - * restricted, its size will not be included. - * - * @param file the regular file or directory to return the size - * of (must not be {@code null}). - * @return the length of the file, or recursive size of the directory, - * provided (in bytes). - * @throws NullPointerException if the file is {@code null} - * @throws IllegalArgumentException if the file does not exist. - * @since 2.0 - */ - public static long sizeOf(File file) { - - if (!file.exists()) { - String message = file + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (file.isDirectory()) { - return sizeOfDirectory(file); - } else { - return file.length(); - } - - } - - /** - * Returns the size of the specified file or directory. If the provided - * {@link java.io.File} is a regular file, then the file's length is returned. - * If the argument is a directory, then the size of the directory is - * calculated recursively. If a directory or subdirectory is security - * restricted, its size will not be included. - * - * @param file the regular file or directory to return the size - * of (must not be {@code null}). - * @return the length of the file, or recursive size of the directory, - * provided (in bytes). - * @throws NullPointerException if the file is {@code null} - * @throws IllegalArgumentException if the file does not exist. - * @since 2.4 - */ - public static BigInteger sizeOfAsBigInteger(File file) { - - if (!file.exists()) { - String message = file + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (file.isDirectory()) { - return sizeOfDirectoryAsBigInteger(file); - } else { - return BigInteger.valueOf(file.length()); - } - - } - - /** - * Counts the size of a directory recursively (sum of the length of all files). - * - * @param directory directory to inspect, must not be {@code null} - * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total - * is greater than {@link Long#MAX_VALUE}. - * @throws NullPointerException if the directory is {@code null} - */ - public static long sizeOfDirectory(File directory) { - checkDirectory(directory); - - final File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - return 0L; - } - long size = 0; - - for (final File file : files) { - try { - if (!isSymlink(file)) { - size += sizeOf(file); - if (size < 0) { - break; - } - } - } catch (IOException ioe) { - // Ignore exceptions caught when asking if a File is a symlink. - } - } - - return size; - } - - /** - * Counts the size of a directory recursively (sum of the length of all files). - * - * @param directory directory to inspect, must not be {@code null} - * @return size of directory in bytes, 0 if directory is security restricted. - * @throws NullPointerException if the directory is {@code null} - * @since 2.4 - */ - public static BigInteger sizeOfDirectoryAsBigInteger(File directory) { - checkDirectory(directory); - - final File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - return BigInteger.ZERO; - } - BigInteger size = BigInteger.ZERO; - - for (final File file : files) { - try { - if (!isSymlink(file)) { - size = size.add(BigInteger.valueOf(sizeOf(file))); - } - } catch (IOException ioe) { - // Ignore exceptions caught when asking if a File is a symlink. - } - } - - return size; - } - - /** - * Checks that the given {@code File} exists and is a directory. - * - * @param directory The {@code File} to check. - * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory. - */ - private static void checkDirectory(File directory) { - if (!directory.exists()) { - throw new IllegalArgumentException(directory + " does not exist"); - } - if (!directory.isDirectory()) { - throw new IllegalArgumentException(directory + " is not a directory"); - } - } - - //----------------------------------------------------------------------- - - /** - * Tests if the specified File is newer than the reference - * File. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param reference the File of which the modification date - * is used, must not be {@code null} - * @return true if the File exists and has been modified more - * recently than the reference File - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist - */ - public static boolean isFileNewer(File file, File reference) { - if (reference == null) { - throw new IllegalArgumentException("No specified reference file"); - } - if (!reference.exists()) { - throw new IllegalArgumentException("The reference file '" - + reference + "' doesn't exist"); - } - return isFileNewer(file, reference.lastModified()); - } - - /** - * Tests if the specified File is newer than the specified - * Date. - * - * @param file the File of which the modification date - * must be compared, must not be {@code null} - * @param date the date reference, must not be {@code null} - * @return true if the File exists and has been modified - * after the given Date. - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the date is {@code null} - */ - public static boolean isFileNewer(File file, Date date) { - if (date == null) { - throw new IllegalArgumentException("No specified date"); - } - return isFileNewer(file, date.getTime()); - } - - /** - * Tests if the specified File is newer than the specified - * time reference. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param timeMillis the time reference measured in milliseconds since the - * epoch (00:00:00 GMT, January 1, 1970) - * @return true if the File exists and has been modified after - * the given time reference. - * @throws IllegalArgumentException if the file is {@code null} - */ - public static boolean isFileNewer(File file, long timeMillis) { - if (file == null) { - throw new IllegalArgumentException("No specified file"); - } - if (!file.exists()) { - return false; - } - return file.lastModified() > timeMillis; - } - - - //----------------------------------------------------------------------- - - /** - * Tests if the specified File is older than the reference - * File. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param reference the File of which the modification date - * is used, must not be {@code null} - * @return true if the File exists and has been modified before - * the reference File - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist - */ - public static boolean isFileOlder(File file, File reference) { - if (reference == null) { - throw new IllegalArgumentException("No specified reference file"); - } - if (!reference.exists()) { - throw new IllegalArgumentException("The reference file '" - + reference + "' doesn't exist"); - } - return isFileOlder(file, reference.lastModified()); - } - - /** - * Tests if the specified File is older than the specified - * Date. - * - * @param file the File of which the modification date - * must be compared, must not be {@code null} - * @param date the date reference, must not be {@code null} - * @return true if the File exists and has been modified - * before the given Date. - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the date is {@code null} - */ - public static boolean isFileOlder(File file, Date date) { - if (date == null) { - throw new IllegalArgumentException("No specified date"); - } - return isFileOlder(file, date.getTime()); - } - - /** - * Tests if the specified File is older than the specified - * time reference. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param timeMillis the time reference measured in milliseconds since the - * epoch (00:00:00 GMT, January 1, 1970) - * @return true if the File exists and has been modified before - * the given time reference. - * @throws IllegalArgumentException if the file is {@code null} - */ - public static boolean isFileOlder(File file, long timeMillis) { - if (file == null) { - throw new IllegalArgumentException("No specified file"); - } - if (!file.exists()) { - return false; - } - return file.lastModified() < timeMillis; - } - - //----------------------------------------------------------------------- - - /** - * Moves a directory. - *

- * When the destination directory is on another file system, do a "copy and delete". - * - * @param srcDir the directory to be moved - * @param destDir the destination directory - * @throws NullPointerException if source or destination is {@code null} - * @throws FileExistsException if the destination directory exists - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveDirectory(File srcDir, File destDir) throws IOException { - if (srcDir == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (!srcDir.exists()) { - throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); - } - if (!srcDir.isDirectory()) { - throw new IOException("Source '" + srcDir + "' is not a directory"); - } - if (destDir.exists()) { - throw new FileExistsException("Destination '" + destDir + "' already exists"); - } - boolean rename = srcDir.renameTo(destDir); - if (!rename) { - if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { - throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir); - } - copyDirectory(srcDir, destDir); - deleteDirectory(srcDir); - if (srcDir.exists()) { - throw new IOException("Failed to delete original directory '" + srcDir + - "' after copy to '" + destDir + "'"); - } - } - } - - /** - * Moves a directory to another directory. - * - * @param src the file to be moved - * @param destDir the destination file - * @param createDestDir If {@code true} create the destination directory, - * otherwise if {@code false} throw an IOException - * @throws NullPointerException if source or destination is {@code null} - * @throws FileExistsException if the directory exists in the destination directory - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException { - if (src == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination directory must not be null"); - } - if (!destDir.exists() && createDestDir) { - destDir.mkdirs(); - } - if (!destDir.exists()) { - throw new FileNotFoundException("Destination directory '" + destDir + - "' does not exist [createDestDir=" + createDestDir + "]"); - } - if (!destDir.isDirectory()) { - throw new IOException("Destination '" + destDir + "' is not a directory"); - } - moveDirectory(src, new File(destDir, src.getName())); - - } - - /** - * Moves a file. - *

- * When the destination file is on another file system, do a "copy and delete". - * - * @param srcFile the file to be moved - * @param destFile the destination file - * @throws NullPointerException if source or destination is {@code null} - * @throws FileExistsException if the destination file exists - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveFile(File srcFile, File destFile) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destFile == null) { - throw new NullPointerException("Destination must not be null"); - } - if (!srcFile.exists()) { - throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); - } - if (srcFile.isDirectory()) { - throw new IOException("Source '" + srcFile + "' is a directory"); - } - if (destFile.exists()) { - throw new FileExistsException("Destination '" + destFile + "' already exists"); - } - if (destFile.isDirectory()) { - throw new IOException("Destination '" + destFile + "' is a directory"); - } - boolean rename = srcFile.renameTo(destFile); - if (!rename) { - copyFile(srcFile, destFile); - if (!srcFile.delete()) { - FileUtils.deleteQuietly(destFile); - throw new IOException("Failed to delete original file '" + srcFile + - "' after copy to '" + destFile + "'"); - } - } - } - - /** - * Moves a file to a directory. - * - * @param srcFile the file to be moved - * @param destDir the destination file - * @param createDestDir If {@code true} create the destination directory, - * otherwise if {@code false} throw an IOException - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination directory must not be null"); - } - if (!destDir.exists() && createDestDir) { - destDir.mkdirs(); - } - if (!destDir.exists()) { - throw new FileNotFoundException("Destination directory '" + destDir + - "' does not exist [createDestDir=" + createDestDir + "]"); - } - if (!destDir.isDirectory()) { - throw new IOException("Destination '" + destDir + "' is not a directory"); - } - moveFile(srcFile, new File(destDir, srcFile.getName())); - } - - /** - * Moves a file or directory to the destination directory. - *

- * When the destination is on another file system, do a "copy and delete". - * - * @param src the file or directory to be moved - * @param destDir the destination directory - * @param createDestDir If {@code true} create the destination directory, - * otherwise if {@code false} throw an IOException - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException { - if (src == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (!src.exists()) { - throw new FileNotFoundException("Source '" + src + "' does not exist"); - } - if (src.isDirectory()) { - moveDirectoryToDirectory(src, destDir, createDestDir); - } else { - moveFileToDirectory(src, destDir, createDestDir); - } - } - - /** - * Determines whether the specified file is a Symbolic Link rather than an actual file. - *

- * Will not return true if there is a Symbolic Link anywhere in the path, - * only if the specific file is. - *

- * Note: the current implementation always returns {@code false} if the system - * is detected as Windows using {@link FilenameUtils#isSystemWindows()} - * - * @param file the file to check - * @return true if the file is a Symbolic Link - * @throws java.io.IOException if an IO error occurs while checking the file - * @since 2.0 - */ - public static boolean isSymlink(File file) throws IOException { - if (file == null) { - throw new NullPointerException("File must not be null"); - } - if (FilenameUtils.isSystemWindows()) { - return false; - } - File fileInCanonicalDir = null; - if (file.getParent() == null) { - fileInCanonicalDir = file; - } else { - File canonicalDir = file.getParentFile().getCanonicalFile(); - fileInCanonicalDir = new File(canonicalDir, file.getName()); - } - - if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { - return false; - } else { - return true; - } - } - - /** - * Indicates that a file already exists. - * - * @version $Id: FileExistsException.java 1304052 2012-03-22 20:55:29Z ggregory $ - * @since 2.0 - */ - public static class FileExistsException extends IOException { - - /** - * Defines the serial version UID. - */ - private static final long serialVersionUID = 1L; - - /** - * Default Constructor. - */ - public FileExistsException() { - super(); - } - - /** - * Construct an instance with the specified message. - * - * @param message The error message - */ - public FileExistsException(String message) { - super(message); - } - - /** - * Construct an instance with the specified file. - * - * @param file The file that exists - */ - public FileExistsException(File file) { - super("File " + file + " exists"); - } - - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import java.io.*; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.util.*; + + +/** + * General file manipulation utilities. + *

+ * Facilities are provided in the following areas: + *

    + *
  • writing to a file + *
  • reading from a file + *
  • make a directory including parent directories + *
  • copying files and directories + *
  • deleting files and directories + *
  • converting to and from a URL + *
  • listing files and directories by filter and extension + *
  • comparing file content + *
  • file last changed date + *
  • calculating a checksum + *
+ *

+ * Origin of code: Excalibur, Alexandria, Commons-Utils + * + * @version $Id: FileUtils.java 1349509 2012-06-12 20:39:23Z ggregory $ + */ +public class FileUtils { + + /** + * Instances should NOT be constructed in standard programming. + */ + public FileUtils() { + super(); + } + + /** + * The number of bytes in a kilobyte. + */ + public static final long ONE_KB = 1024; + + /** + * The number of bytes in a kilobyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB); + + /** + * The number of bytes in a megabyte. + */ + public static final long ONE_MB = ONE_KB * ONE_KB; + + /** + * The number of bytes in a megabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI); + + /** + * The file copy buffer size (30 MB) + */ + private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; + + /** + * The number of bytes in a gigabyte. + */ + public static final long ONE_GB = ONE_KB * ONE_MB; + + /** + * The number of bytes in a gigabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI); + + /** + * The number of bytes in a terabyte. + */ + public static final long ONE_TB = ONE_KB * ONE_GB; + + /** + * The number of bytes in a terabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI); + + /** + * The number of bytes in a petabyte. + */ + public static final long ONE_PB = ONE_KB * ONE_TB; + + /** + * The number of bytes in a petabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI); + + /** + * The number of bytes in an exabyte. + */ + public static final long ONE_EB = ONE_KB * ONE_PB; + + /** + * The number of bytes in an exabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI); + + /** + * The number of bytes in a zettabyte. + */ + public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB)); + + /** + * The number of bytes in a yottabyte. + */ + public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB); + + /** + * An empty array of type File. + */ + public static final File[] EMPTY_FILE_ARRAY = new File[0]; + + /** + * The UTF-8 character set, used to decode octets in URLs. + */ + private static final Charset UTF8 = Charset.forName("UTF-8"); + + //----------------------------------------------------------------------- + + /** + * Construct a file from the set of name elements. + * + * @param directory the parent directory + * @param names the name elements + * @return the file + * @since 2.1 + */ + public static File getFile(File directory, String... names) { + if (directory == null) { + throw new NullPointerException("directorydirectory must not be null"); + } + if (names == null) { + throw new NullPointerException("names must not be null"); + } + File file = directory; + for (String name : names) { + file = new File(file, name); + } + return file; + } + + /** + * Construct a file from the set of name elements. + * + * @param names the name elements + * @return the file + * @since 2.1 + */ + public static File getFile(String... names) { + if (names == null) { + throw new NullPointerException("names must not be null"); + } + File file = null; + for (String name : names) { + if (file == null) { + file = new File(name); + } else { + file = new File(file, name); + } + } + return file; + } + + /** + * Returns the path to the system temporary directory. + * + * @return the path to the system temporary directory. + * @since 2.0 + */ + public static String getTempDirectoryPath() { + return System.getProperty("java.io.tmpdir"); + } + + /** + * Returns a {@link java.io.File} representing the system temporary directory. + * + * @return the system temporary directory. + * @since 2.0 + */ + public static File getTempDirectory() { + return new File(getTempDirectoryPath()); + } + + /** + * Returns the path to the user's home directory. + * + * @return the path to the user's home directory. + * @since 2.0 + */ + public static String getUserDirectoryPath() { + return System.getProperty("user.home"); + } + + /** + * Returns a {@link java.io.File} representing the user's home directory. + * + * @return the user's home directory. + * @since 2.0 + */ + public static File getUserDirectory() { + return new File(getUserDirectoryPath()); + } + + //----------------------------------------------------------------------- + + /** + * Opens a {@link java.io.FileInputStream} for the specified file, providing better + * error messages than simply calling new FileInputStream(file). + *

+ * At the end of the method either the stream will be successfully opened, + * or an exception will have been thrown. + *

+ * An exception is thrown if the file does not exist. + * An exception is thrown if the file object exists but is a directory. + * An exception is thrown if the file exists but cannot be read. + * + * @param file the file to open for input, must not be {@code null} + * @return a new {@link java.io.FileInputStream} for the specified file + * @throws java.io.FileNotFoundException if the file does not exist + * @throws java.io.IOException if the file object is a directory + * @throws java.io.IOException if the file cannot be read + * @since 1.3 + */ + public static FileInputStream openInputStream(File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + if (file.canRead() == false) { + throw new IOException("File '" + file + "' cannot be read"); + } + } else { + throw new FileNotFoundException("File '" + file + "' does not exist"); + } + return new FileInputStream(file); + } + + //----------------------------------------------------------------------- + + /** + * Opens a {@link java.io.FileOutputStream} for the specified file, checking and + * creating the parent directory if it does not exist. + *

+ * At the end of the method either the stream will be successfully opened, + * or an exception will have been thrown. + *

+ * The parent directory will be created if it does not exist. + * The file will be created if it does not exist. + * An exception is thrown if the file object exists but is a directory. + * An exception is thrown if the file exists but cannot be written to. + * An exception is thrown if the parent directory cannot be created. + * + * @param file the file to open for output, must not be {@code null} + * @return a new {@link java.io.FileOutputStream} for the specified file + * @throws java.io.IOException if the file object is a directory + * @throws java.io.IOException if the file cannot be written to + * @throws java.io.IOException if a parent directory needs creating but that fails + * @since 1.3 + */ + public static FileOutputStream openOutputStream(File file) throws IOException { + return openOutputStream(file, false); + } + + /** + * Opens a {@link java.io.FileOutputStream} for the specified file, checking and + * creating the parent directory if it does not exist. + *

+ * At the end of the method either the stream will be successfully opened, + * or an exception will have been thrown. + *

+ * The parent directory will be created if it does not exist. + * The file will be created if it does not exist. + * An exception is thrown if the file object exists but is a directory. + * An exception is thrown if the file exists but cannot be written to. + * An exception is thrown if the parent directory cannot be created. + * + * @param file the file to open for output, must not be {@code null} + * @param append if {@code true}, then bytes will be added to the + * end of the file rather than overwriting + * @return a new {@link java.io.FileOutputStream} for the specified file + * @throws java.io.IOException if the file object is a directory + * @throws java.io.IOException if the file cannot be written to + * @throws java.io.IOException if a parent directory needs creating but that fails + * @since 2.1 + */ + public static FileOutputStream openOutputStream(File file, boolean append) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + if (file.canWrite() == false) { + throw new IOException("File '" + file + "' cannot be written to"); + } + } else { + File parent = file.getParentFile(); + if (parent != null) { + if (!parent.mkdirs() && !parent.isDirectory()) { + throw new IOException("Directory '" + parent + "' could not be created"); + } + } + } + return new FileOutputStream(file, append); + } + + //----------------------------------------------------------------------- + + /** + * Returns a human-readable version of the file size, where the input represents a specific number of bytes. + *

+ * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the + * nearest GB boundary. + *

+ *

+ * Similarly for the 1MB and 1KB boundaries. + *

+ * + * @param size the number of bytes + * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) + * @see IO-226 - should the rounding be changed? + * @since 2.4 + */ + // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? + public static String byteCountToDisplaySize(BigInteger size) { + String displaySize; + + if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_EB_BI)) + " EB"; + } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_PB_BI)) + " PB"; + } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_TB_BI)) + " TB"; + } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_GB_BI)) + " GB"; + } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_MB_BI)) + " MB"; + } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_KB_BI)) + " KB"; + } else { + displaySize = String.valueOf(size) + " bytes"; + } + return displaySize; + } + + /** + * Returns a human-readable version of the file size, where the input represents a specific number of bytes. + *

+ * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the + * nearest GB boundary. + *

+ *

+ * Similarly for the 1MB and 1KB boundaries. + *

+ * + * @param size the number of bytes + * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) + * @see IO-226 - should the rounding be changed? + */ + // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? + public static String byteCountToDisplaySize(long size) { + return byteCountToDisplaySize(BigInteger.valueOf(size)); + } + + //----------------------------------------------------------------------- + + /** + * Implements the same behaviour as the "touch" utility on Unix. It creates + * a new file with size 0 or, if the file exists already, it is opened and + * closed without modifying it, but updating the file date and time. + *

+ * NOTE: As from v1.3, this method throws an IOException if the last + * modified date of the file cannot be set. Also, as from v1.3 this method + * creates parent directories if they do not exist. + * + * @param file the File to touch + * @throws java.io.IOException If an I/O problem occurs + */ + public static void touch(File file) throws IOException { + if (!file.exists()) { + OutputStream out = openOutputStream(file); + IOUtils.closeQuietly(out); + } + boolean success = file.setLastModified(System.currentTimeMillis()); + if (!success) { + throw new IOException("Unable to set the last modification time for " + file); + } + } + + //----------------------------------------------------------------------- + + /** + * Converts a Collection containing java.io.File instanced into array + * representation. This is to account for the difference between + * File.listFiles() and FileUtils.listFiles(). + * + * @param files a Collection containing java.io.File instances + * @return an array of java.io.File + */ + public static File[] convertFileCollectionToFileArray(Collection files) { + return files.toArray(new File[files.size()]); + } + + //----------------------------------------------------------------------- + + /** + * Converts an array of file extensions to suffixes for use + * with IOFileFilters. + * + * @param extensions an array of extensions. Format: {"java", "xml"} + * @return an array of suffixes. Format: {".java", ".xml"} + */ + private static String[] toSuffixes(String[] extensions) { + String[] suffixes = new String[extensions.length]; + for (int i = 0; i < extensions.length; i++) { + suffixes[i] = "." + extensions[i]; + } + return suffixes; + } + + //----------------------------------------------------------------------- + + /** + * Compares the contents of two files to determine if they are equal or not. + *

+ * This method checks to see if the two files are different lengths + * or if they point to the same file, before resorting to byte-by-byte + * comparison of the contents. + *

+ * Code origin: Avalon + * + * @param file1 the first file + * @param file2 the second file + * @return true if the content of the files are equal or they both don't + * exist, false otherwise + * @throws java.io.IOException in case of an I/O error + */ + public static boolean contentEquals(File file1, File file2) throws IOException { + boolean file1Exists = file1.exists(); + if (file1Exists != file2.exists()) { + return false; + } + + if (!file1Exists) { + // two not existing files are equal + return true; + } + + if (file1.isDirectory() || file2.isDirectory()) { + // don't want to compare directory contents + throw new IOException("Can't compare directories, only files"); + } + + if (file1.length() != file2.length()) { + // lengths differ, cannot be equal + return false; + } + + if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { + // same file + return true; + } + + InputStream input1 = null; + InputStream input2 = null; + try { + input1 = new FileInputStream(file1); + input2 = new FileInputStream(file2); + return IOUtils.contentEquals(input1, input2); + + } finally { + IOUtils.closeQuietly(input1); + IOUtils.closeQuietly(input2); + } + } + + //----------------------------------------------------------------------- + + /** + * Compares the contents of two files to determine if they are equal or not. + *

+ * This method checks to see if the two files point to the same file, + * before resorting to line-by-line comparison of the contents. + *

+ * + * @param file1 the first file + * @param file2 the second file + * @param charsetName the character encoding to be used. + * May be null, in which case the platform default is used + * @return true if the content of the files are equal or neither exists, + * false otherwise + * @throws java.io.IOException in case of an I/O error + * @see IOUtils#contentEqualsIgnoreEOL(java.io.Reader, java.io.Reader) + * @since 2.2 + */ + public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException { + boolean file1Exists = file1.exists(); + if (file1Exists != file2.exists()) { + return false; + } + + if (!file1Exists) { + // two not existing files are equal + return true; + } + + if (file1.isDirectory() || file2.isDirectory()) { + // don't want to compare directory contents + throw new IOException("Can't compare directories, only files"); + } + + if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { + // same file + return true; + } + + Reader input1 = null; + Reader input2 = null; + try { + if (charsetName == null) { + input1 = new InputStreamReader(new FileInputStream(file1)); + input2 = new InputStreamReader(new FileInputStream(file2)); + } else { + input1 = new InputStreamReader(new FileInputStream(file1), charsetName); + input2 = new InputStreamReader(new FileInputStream(file2), charsetName); + } + return IOUtils.contentEqualsIgnoreEOL(input1, input2); + + } finally { + IOUtils.closeQuietly(input1); + IOUtils.closeQuietly(input2); + } + } + + //----------------------------------------------------------------------- + + /** + * Convert from a URL to a File. + *

+ * From version 1.1 this method will decode the URL. + * Syntax such as file:///my%20docs/file.txt will be + * correctly decoded to /my docs/file.txt. Starting with version + * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters. + * Additionally, malformed percent-encoded octets are handled leniently by + * passing them through literally. + * + * @param url the file URL to convert, {@code null} returns {@code null} + * @return the equivalent File object, or {@code null} + * if the URL's protocol is not file + */ + public static File toFile(URL url) { + if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) { + return null; + } else { + String filename = url.getFile().replace('/', File.separatorChar); + filename = decodeUrl(filename); + return new File(filename); + } + } + + /** + * Decodes the specified URL as per RFC 3986, i.e. transforms + * percent-encoded octets to characters by decoding with the UTF-8 character + * set. This function is primarily intended for usage with + * {@link java.net.URL} which unfortunately does not enforce proper URLs. As + * such, this method will leniently accept invalid characters or malformed + * percent-encoded octets and simply pass them literally through to the + * result string. Except for rare edge cases, this will make unencoded URLs + * pass through unaltered. + * + * @param url The URL to decode, may be {@code null}. + * @return The decoded URL or {@code null} if the input was + * {@code null}. + */ + static String decodeUrl(String url) { + String decoded = url; + if (url != null && url.indexOf('%') >= 0) { + int n = url.length(); + StringBuffer buffer = new StringBuffer(); + ByteBuffer bytes = ByteBuffer.allocate(n); + for (int i = 0; i < n; ) { + if (url.charAt(i) == '%') { + try { + do { + byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); + bytes.put(octet); + i += 3; + } while (i < n && url.charAt(i) == '%'); + continue; + } catch (RuntimeException e) { + // malformed percent-encoded octet, fall through and + // append characters literally + } finally { + if (bytes.position() > 0) { + bytes.flip(); + buffer.append(UTF8.decode(bytes).toString()); + bytes.clear(); + } + } + } + buffer.append(url.charAt(i++)); + } + decoded = buffer.toString(); + } + return decoded; + } + + /** + * Converts each of an array of URL to a File. + *

+ * Returns an array of the same size as the input. + * If the input is {@code null}, an empty array is returned. + * If the input contains {@code null}, the output array contains {@code null} at the same + * index. + *

+ * This method will decode the URL. + * Syntax such as file:///my%20docs/file.txt will be + * correctly decoded to /my docs/file.txt. + * + * @param urls the file URLs to convert, {@code null} returns empty array + * @return a non-{@code null} array of Files matching the input, with a {@code null} item + * if there was a {@code null} at that index in the input array + * @throws IllegalArgumentException if any file is not a URL file + * @throws IllegalArgumentException if any file is incorrectly encoded + * @since 1.1 + */ + public static File[] toFiles(URL[] urls) { + if (urls == null || urls.length == 0) { + return EMPTY_FILE_ARRAY; + } + File[] files = new File[urls.length]; + for (int i = 0; i < urls.length; i++) { + URL url = urls[i]; + if (url != null) { + if (url.getProtocol().equals("file") == false) { + throw new IllegalArgumentException( + "URL could not be converted to a File: " + url); + } + files[i] = toFile(url); + } + } + return files; + } + + /** + * Converts each of an array of File to a URL. + *

+ * Returns an array of the same size as the input. + * + * @param files the files to convert, must not be {@code null} + * @return an array of URLs matching the input + * @throws java.io.IOException if a file cannot be converted + * @throws NullPointerException if the parameter is null + */ + public static URL[] toURLs(File[] files) throws IOException { + URL[] urls = new URL[files.length]; + + for (int i = 0; i < urls.length; i++) { + urls[i] = files[i].toURI().toURL(); + } + + return urls; + } + + //----------------------------------------------------------------------- + + /** + * Copies a file to a directory preserving the file date. + *

+ * This method copies the contents of the specified source file + * to a file of the same name in the specified destination directory. + * The destination directory is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

+ * Note: This method tries to preserve the file's last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destDir the directory to place the copy in, must not be {@code null} + * @throws NullPointerException if source or destination is null + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFile(java.io.File, java.io.File, boolean) + */ + public static void copyFileToDirectory(File srcFile, File destDir) throws IOException { + copyFileToDirectory(srcFile, destDir, true); + } + + /** + * Copies a file to a directory optionally preserving the file date. + *

+ * This method copies the contents of the specified source file + * to a file of the same name in the specified destination directory. + * The destination directory is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

+ * Note: Setting preserveFileDate to + * {@code true} tries to preserve the file's last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destDir the directory to place the copy in, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFile(java.io.File, java.io.File, boolean) + * @since 1.3 + */ + public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException { + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (destDir.exists() && destDir.isDirectory() == false) { + throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); + } + File destFile = new File(destDir, srcFile.getName()); + copyFile(srcFile, destFile, preserveFileDate); + } + + /** + * Copies a file to a new location preserving the file date. + *

+ * This method copies the contents of the specified source file to the + * specified destination file. The directory holding the destination file is + * created if it does not exist. If the destination file exists, then this + * method will overwrite it. + *

+ * Note: This method tries to preserve the file's last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destFile the new file, must not be {@code null} + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFileToDirectory(java.io.File, java.io.File) + */ + public static void copyFile(File srcFile, File destFile) throws IOException { + copyFile(srcFile, destFile, true); + } + + /** + * Copies a file to a new location. + *

+ * This method copies the contents of the specified source file + * to the specified destination file. + * The directory holding the destination file is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

+ * Note: Setting preserveFileDate to + * {@code true} tries to preserve the file's last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destFile the new file, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFileToDirectory(java.io.File, java.io.File, boolean) + */ + public static void copyFile(File srcFile, File destFile, + boolean preserveFileDate) throws IOException { + if (srcFile == null) { + throw new NullPointerException("Source must not be null"); + } + if (destFile == null) { + throw new NullPointerException("Destination must not be null"); + } + if (srcFile.exists() == false) { + throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); + } + if (srcFile.isDirectory()) { + throw new IOException("Source '" + srcFile + "' exists but is a directory"); + } + if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { + throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); + } + File parentFile = destFile.getParentFile(); + if (parentFile != null) { + if (!parentFile.mkdirs() && !parentFile.isDirectory()) { + throw new IOException("Destination '" + parentFile + "' directory cannot be created"); + } + } + if (destFile.exists() && destFile.canWrite() == false) { + throw new IOException("Destination '" + destFile + "' exists but is read-only"); + } + doCopyFile(srcFile, destFile, preserveFileDate); + } + + /** + * Copy bytes from a File to an OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a BufferedInputStream. + *

+ * + * @param input the File to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.1 + */ + public static long copyFile(File input, OutputStream output) throws IOException { + final FileInputStream fis = new FileInputStream(input); + try { + return IOUtils.copyLarge(fis, output); + } finally { + fis.close(); + } + } + + /** + * Internal copy file method. + * + * @param srcFile the validated source file, must not be {@code null} + * @param destFile the validated destination file, must not be {@code null} + * @param preserveFileDate whether to preserve the file date + * @throws java.io.IOException if an error occurs + */ + private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { + if (destFile.exists() && destFile.isDirectory()) { + throw new IOException("Destination '" + destFile + "' exists but is a directory"); + } + + FileInputStream fis = null; + FileOutputStream fos = null; + FileChannel input = null; + FileChannel output = null; + try { + fis = new FileInputStream(srcFile); + fos = new FileOutputStream(destFile); + input = fis.getChannel(); + output = fos.getChannel(); + long size = input.size(); + long pos = 0; + long count = 0; + while (pos < size) { + count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos; + pos += output.transferFrom(input, pos, count); + } + } finally { + IOUtils.closeQuietly(output); + IOUtils.closeQuietly(fos); + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(fis); + } + + if (srcFile.length() != destFile.length()) { + throw new IOException("Failed to copy full contents from '" + + srcFile + "' to '" + destFile + "'"); + } + if (preserveFileDate) { + destFile.setLastModified(srcFile.lastModified()); + } + } + + //----------------------------------------------------------------------- + + /** + * Copies a directory to within another directory preserving the file dates. + *

+ * This method copies the source directory and all its contents to a + * directory of the same name in the specified destination directory. + *

+ * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

+ * Note: This method tries to preserve the files' last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the directory to place the copy in, must not be {@code null} + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.2 + */ + public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException { + if (srcDir == null) { + throw new NullPointerException("Source must not be null"); + } + if (srcDir.exists() && srcDir.isDirectory() == false) { + throw new IllegalArgumentException("Source '" + destDir + "' is not a directory"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (destDir.exists() && destDir.isDirectory() == false) { + throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); + } + copyDirectory(srcDir, new File(destDir, srcDir.getName()), true); + } + + /** + * Copies a whole directory to a new location preserving the file dates. + *

+ * This method copies the specified directory and all its child + * directories and files to the specified destination. + * The destination is the new location and name of the directory. + *

+ * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

+ * Note: This method tries to preserve the files' last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.1 + */ + public static void copyDirectory(File srcDir, File destDir) throws IOException { + copyDirectory(srcDir, destDir, true); + } + + /** + * Copies a whole directory to a new location. + *

+ * This method copies the contents of the specified source directory + * to within the specified destination directory. + *

+ * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

+ * Note: Setting preserveFileDate to + * {@code true} tries to preserve the files' last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.1 + */ + public static void copyDirectory(File srcDir, File destDir, + boolean preserveFileDate) throws IOException { + copyDirectory(srcDir, destDir, null, preserveFileDate); + } + + /** + * Copies a filtered directory to a new location preserving the file dates. + *

+ * This method copies the contents of the specified source directory + * to within the specified destination directory. + *

+ * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

+ * Note: This method tries to preserve the files' last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + *

+ *

Example: Copy directories only

+ *
+     *  // only copy the directory structure
+     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
+     *  
+ * + *

Example: Copy directories and txt files

+ *
+     *  // Create a filter for ".txt" files
+     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
+     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+     *
+     *  // Create a filter for either directories or ".txt" files
+     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+     *
+     *  // Copy using the filter
+     *  FileUtils.copyDirectory(srcDir, destDir, filter);
+     *  
+ * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @param filter the filter to apply, null means copy all directories and files + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.4 + */ + public static void copyDirectory(File srcDir, File destDir, + FileFilter filter) throws IOException { + copyDirectory(srcDir, destDir, filter, true); + } + + /** + * Copies a filtered directory to a new location. + *

+ * This method copies the contents of the specified source directory + * to within the specified destination directory. + *

+ * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

+ * Note: Setting preserveFileDate to + * {@code true} tries to preserve the files' last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + *

+ *

Example: Copy directories only

+ *
+     *  // only copy the directory structure
+     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
+     *  
+ * + *

Example: Copy directories and txt files

+ *
+     *  // Create a filter for ".txt" files
+     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
+     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
+     *
+     *  // Create a filter for either directories or ".txt" files
+     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
+     *
+     *  // Copy using the filter
+     *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
+     *  
+ * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @param filter the filter to apply, null means copy all directories and files + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.4 + */ + public static void copyDirectory(File srcDir, File destDir, + FileFilter filter, boolean preserveFileDate) throws IOException { + if (srcDir == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (srcDir.exists() == false) { + throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); + } + if (srcDir.isDirectory() == false) { + throw new IOException("Source '" + srcDir + "' exists but is not a directory"); + } + if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) { + throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); + } + + // Cater for destination being directory within the source directory (see IO-141) + List exclusionList = null; + if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { + File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); + if (srcFiles != null && srcFiles.length > 0) { + exclusionList = new ArrayList(srcFiles.length); + for (File srcFile : srcFiles) { + File copiedFile = new File(destDir, srcFile.getName()); + exclusionList.add(copiedFile.getCanonicalPath()); + } + } + } + doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList); + } + + /** + * Internal copy directory method. + * + * @param srcDir the validated source directory, must not be {@code null} + * @param destDir the validated destination directory, must not be {@code null} + * @param filter the filter to apply, null means copy all directories and files + * @param preserveFileDate whether to preserve the file date + * @param exclusionList List of files and directories to exclude from the copy, may be null + * @throws java.io.IOException if an error occurs + * @since 1.1 + */ + private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter, + boolean preserveFileDate, List exclusionList) throws IOException { + // recurse + File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); + if (srcFiles == null) { // null if abstract pathname does not denote a directory, or if an I/O error occurs + throw new IOException("Failed to list contents of " + srcDir); + } + if (destDir.exists()) { + if (destDir.isDirectory() == false) { + throw new IOException("Destination '" + destDir + "' exists but is not a directory"); + } + } else { + if (!destDir.mkdirs() && !destDir.isDirectory()) { + throw new IOException("Destination '" + destDir + "' directory cannot be created"); + } + } + if (destDir.canWrite() == false) { + throw new IOException("Destination '" + destDir + "' cannot be written to"); + } + for (File srcFile : srcFiles) { + File dstFile = new File(destDir, srcFile.getName()); + if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) { + if (srcFile.isDirectory()) { + doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList); + } else { + doCopyFile(srcFile, dstFile, preserveFileDate); + } + } + } + + // Do this last, as the above has probably affected directory metadata + if (preserveFileDate) { + destDir.setLastModified(srcDir.lastModified()); + } + } + + //----------------------------------------------------------------------- + + /** + * Copies bytes from the URL source to a file + * destination. The directories up to destination + * will be created if they don't already exist. destination + * will be overwritten if it already exists. + *

+ * Warning: this method does not set a connection or read timeout and thus + * might block forever. Use {@link #copyURLToFile(java.net.URL, java.io.File, int, int)} + * with reasonable timeouts to prevent this. + * + * @param source the URL to copy bytes from, must not be {@code null} + * @param destination the non-directory File to write bytes to + * (possibly overwriting), must not be {@code null} + * @throws java.io.IOException if source URL cannot be opened + * @throws java.io.IOException if destination is a directory + * @throws java.io.IOException if destination cannot be written + * @throws java.io.IOException if destination needs creating but can't be + * @throws java.io.IOException if an IO error occurs during copying + */ + public static void copyURLToFile(URL source, File destination) throws IOException { + InputStream input = source.openStream(); + copyInputStreamToFile(input, destination); + } + + /** + * Copies bytes from the URL source to a file + * destination. The directories up to destination + * will be created if they don't already exist. destination + * will be overwritten if it already exists. + * + * @param source the URL to copy bytes from, must not be {@code null} + * @param destination the non-directory File to write bytes to + * (possibly overwriting), must not be {@code null} + * @param connectionTimeout the number of milliseconds until this method + * will timeout if no connection could be established to the source + * @param readTimeout the number of milliseconds until this method will + * timeout if no data could be read from the source + * @throws java.io.IOException if source URL cannot be opened + * @throws java.io.IOException if destination is a directory + * @throws java.io.IOException if destination cannot be written + * @throws java.io.IOException if destination needs creating but can't be + * @throws java.io.IOException if an IO error occurs during copying + * @since 2.0 + */ + public static void copyURLToFile(URL source, File destination, + int connectionTimeout, int readTimeout) throws IOException { + URLConnection connection = source.openConnection(); + connection.setConnectTimeout(connectionTimeout); + connection.setReadTimeout(readTimeout); + InputStream input = connection.getInputStream(); + copyInputStreamToFile(input, destination); + } + + /** + * Copies bytes from an {@link java.io.InputStream} source to a file + * destination. The directories up to destination + * will be created if they don't already exist. destination + * will be overwritten if it already exists. + * + * @param source the InputStream to copy bytes from, must not be {@code null} + * @param destination the non-directory File to write bytes to + * (possibly overwriting), must not be {@code null} + * @throws java.io.IOException if destination is a directory + * @throws java.io.IOException if destination cannot be written + * @throws java.io.IOException if destination needs creating but can't be + * @throws java.io.IOException if an IO error occurs during copying + * @since 2.0 + */ + public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { + try { + FileOutputStream output = openOutputStream(destination); + try { + IOUtils.copy(source, output); + output.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(output); + } + } finally { + IOUtils.closeQuietly(source); + } + } + + //----------------------------------------------------------------------- + + /** + * Deletes a directory recursively. + * + * @param directory directory to delete + * @throws java.io.IOException in case deletion is unsuccessful + */ + public static void deleteDirectory(File directory) throws IOException { + if (!directory.exists()) { + return; + } + + if (!isSymlink(directory)) { + cleanDirectory(directory); + } + + if (!directory.delete()) { + String message = + "Unable to delete directory " + directory + "."; + throw new IOException(message); + } + } + + /** + * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. + *

+ * The difference between File.delete() and this method are: + *

    + *
  • A directory to be deleted does not have to be empty.
  • + *
  • No exceptions are thrown when a file or directory cannot be deleted.
  • + *
+ * + * @param file file or directory to delete, can be {@code null} + * @return {@code true} if the file or directory was deleted, otherwise + * {@code false} + * @since 1.4 + */ + public static boolean deleteQuietly(File file) { + if (file == null) { + return false; + } + try { + if (file.isDirectory()) { + cleanDirectory(file); + } + } catch (Exception ignored) { + } + + try { + return file.delete(); + } catch (Exception ignored) { + return false; + } + } + + /** + * Cleans a directory without deleting it. + * + * @param directory directory to clean + * @throws java.io.IOException in case cleaning is unsuccessful + */ + public static void cleanDirectory(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + forceDelete(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + //----------------------------------------------------------------------- + + /** + * Waits for NFS to propagate a file creation, imposing a timeout. + *

+ * This method repeatedly tests {@link java.io.File#exists()} until it returns + * true up to the maximum time specified in seconds. + * + * @param file the file to check, must not be {@code null} + * @param seconds the maximum time in seconds to wait + * @return true if file exists + * @throws NullPointerException if the file is {@code null} + */ + public static boolean waitFor(File file, int seconds) { + int timeout = 0; + int tick = 0; + while (!file.exists()) { + if (tick++ >= 10) { + tick = 0; + if (timeout++ > seconds) { + return false; + } + } + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + // ignore exception + } catch (Exception ex) { + break; + } + } + return true; + } + + //----------------------------------------------------------------------- + + /** + * Reads the contents of a file into a String. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static String readFileToString(File file, Charset encoding) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.toString(in, Charsets.toCharset(encoding)); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Reads the contents of a file into a String. The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.3 + */ + public static String readFileToString(File file, String encoding) throws IOException { + return readFileToString(file, Charsets.toCharset(encoding)); + } + + + /** + * Reads the contents of a file into a String using the default encoding for the VM. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 1.3.1 + */ + public static String readFileToString(File file) throws IOException { + return readFileToString(file, Charset.defaultCharset()); + } + + /** + * Reads the contents of a file into a byte array. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 1.1 + */ + public static byte[] readFileToByteArray(File file) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.toByteArray(in, file.length()); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Reads the contents of a file line by line to a List of Strings. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the list of Strings representing each line in the file, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static List readLines(File file, Charset encoding) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.readLines(in, Charsets.toCharset(encoding)); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Reads the contents of a file line by line to a List of Strings. The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the list of Strings representing each line in the file, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static List readLines(File file, String encoding) throws IOException { + return readLines(file, Charsets.toCharset(encoding)); + } + + /** + * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the list of Strings representing each line in the file, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 1.3 + */ + public static List readLines(File file) throws IOException { + return readLines(file, Charset.defaultCharset()); + } + + //----------------------------------------------------------------------- + + /** + * Writes a String to a file creating the file if it does not exist. + *

+ * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.4 + */ + public static void writeStringToFile(File file, String data, Charset encoding) throws IOException { + writeStringToFile(file, data, encoding, false); + } + + /** + * Writes a String to a file creating the file if it does not exist. + *

+ * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + */ + public static void writeStringToFile(File file, String data, String encoding) throws IOException { + writeStringToFile(file, data, encoding, false); + } + + /** + * Writes a String to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the String will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException { + OutputStream out = null; + try { + out = openOutputStream(file, append); + IOUtils.write(data, out, encoding); + out.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Writes a String to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the String will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported by the VM + * @since 2.1 + */ + public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException { + writeStringToFile(file, data, Charsets.toCharset(encoding), append); + } + + /** + * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @throws java.io.IOException in case of an I/O error + */ + public static void writeStringToFile(File file, String data) throws IOException { + writeStringToFile(file, data, Charset.defaultCharset(), false); + } + + /** + * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @param append if {@code true}, then the String will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void writeStringToFile(File file, String data, boolean append) throws IOException { + writeStringToFile(file, data, Charset.defaultCharset(), append); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @throws java.io.IOException in case of an I/O error + * @since 2.0 + */ + public static void write(File file, CharSequence data) throws IOException { + write(file, data, Charset.defaultCharset(), false); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @param append if {@code true}, then the data will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void write(File file, CharSequence data, boolean append) throws IOException { + write(file, data, Charset.defaultCharset(), append); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static void write(File file, CharSequence data, Charset encoding) throws IOException { + write(file, data, encoding, false); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.0 + */ + public static void write(File file, CharSequence data, String encoding) throws IOException { + write(file, data, encoding, false); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the data will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static void write(File file, CharSequence data, Charset encoding, boolean append) throws IOException { + String str = data == null ? null : data.toString(); + writeStringToFile(file, str, encoding, append); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the data will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported by the VM + * @since IO 2.1 + */ + public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException { + write(file, data, Charsets.toCharset(encoding), append); + } + + /** + * Writes a byte array to a file creating the file if it does not exist. + *

+ * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @throws java.io.IOException in case of an I/O error + * @since 1.1 + */ + public static void writeByteArrayToFile(File file, byte[] data) throws IOException { + writeByteArrayToFile(file, data, false); + } + + /** + * Writes a byte array to a file creating the file if it does not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @param append if {@code true}, then bytes will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since IO 2.1 + */ + public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException { + OutputStream out = null; + try { + out = openOutputStream(file, append); + out.write(data); + out.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The specified character encoding and the default line ending will be used. + *

+ * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 1.1 + */ + public static void writeLines(File file, String encoding, Collection lines) throws IOException { + writeLines(file, encoding, lines, null, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line, optionally appending. + * The specified character encoding and the default line ending will be used. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.1 + */ + public static void writeLines(File file, String encoding, Collection lines, boolean append) throws IOException { + writeLines(file, encoding, lines, null, append); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the default line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @throws java.io.IOException in case of an I/O error + * @since 1.3 + */ + public static void writeLines(File file, Collection lines) throws IOException { + writeLines(file, null, lines, null, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the default line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void writeLines(File file, Collection lines, boolean append) throws IOException { + writeLines(file, null, lines, null, append); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The specified character encoding and the line ending will be used. + *

+ * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 1.1 + */ + public static void writeLines(File file, String encoding, Collection lines, String lineEnding) + throws IOException { + writeLines(file, encoding, lines, lineEnding, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The specified character encoding and the line ending will be used. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.1 + */ + public static void writeLines(File file, String encoding, Collection lines, String lineEnding, boolean append) + throws IOException { + FileOutputStream out = null; + try { + out = openOutputStream(file, append); + final BufferedOutputStream buffer = new BufferedOutputStream(out); + IOUtils.writeLines(lines, lineEnding, buffer, encoding); + buffer.flush(); + out.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the specified line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @throws java.io.IOException in case of an I/O error + * @since 1.3 + */ + public static void writeLines(File file, Collection lines, String lineEnding) throws IOException { + writeLines(file, null, lines, lineEnding, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the specified line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void writeLines(File file, Collection lines, String lineEnding, boolean append) + throws IOException { + writeLines(file, null, lines, lineEnding, append); + } + + //----------------------------------------------------------------------- + + /** + * Deletes a file. If file is a directory, delete it and all sub-directories. + *

+ * The difference between File.delete() and this method are: + *

    + *
  • A directory to be deleted does not have to be empty.
  • + *
  • You get exceptions when a file or directory cannot be deleted. + * (java.io.File methods returns a boolean)
  • + *
+ * + * @param file file or directory to delete, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.FileNotFoundException if the file was not found + * @throws java.io.IOException in case deletion is unsuccessful + */ + public static void forceDelete(File file) throws IOException { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + boolean filePresent = file.exists(); + if (!file.delete()) { + if (!filePresent) { + throw new FileNotFoundException("File does not exist: " + file); + } + String message = + "Unable to delete file: " + file; + throw new IOException(message); + } + } + } + + /** + * Schedules a file to be deleted when JVM exits. + * If file is directory delete it and all sub-directories. + * + * @param file file or directory to delete, must not be {@code null} + * @throws NullPointerException if the file is {@code null} + * @throws java.io.IOException in case deletion is unsuccessful + */ + public static void forceDeleteOnExit(File file) throws IOException { + if (file.isDirectory()) { + deleteDirectoryOnExit(file); + } else { + file.deleteOnExit(); + } + } + + /** + * Schedules a directory recursively for deletion on JVM exit. + * + * @param directory directory to delete, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.IOException in case deletion is unsuccessful + */ + private static void deleteDirectoryOnExit(File directory) throws IOException { + if (!directory.exists()) { + return; + } + + directory.deleteOnExit(); + if (!isSymlink(directory)) { + cleanDirectoryOnExit(directory); + } + } + + /** + * Cleans a directory without deleting it. + * + * @param directory directory to clean, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.IOException in case cleaning is unsuccessful + */ + private static void cleanDirectoryOnExit(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + forceDeleteOnExit(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + /** + * Makes a directory, including any necessary but nonexistent parent + * directories. If a file already exists with specified name but it is + * not a directory then an IOException is thrown. + * If the directory cannot be created (or does not already exist) + * then an IOException is thrown. + * + * @param directory directory to create, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.IOException if the directory cannot be created or the file already exists but is not a directory + */ + public static void forceMkdir(File directory) throws IOException { + if (directory.exists()) { + if (!directory.isDirectory()) { + String message = + "File " + + directory + + " exists and is " + + "not a directory. Unable to create directory."; + throw new IOException(message); + } + } else { + if (!directory.mkdirs()) { + // Double-check that some other thread or process hasn't made + // the directory in the background + if (!directory.isDirectory()) { + String message = + "Unable to create directory " + directory; + throw new IOException(message); + } + } + } + } + + //----------------------------------------------------------------------- + + /** + * Returns the size of the specified file or directory. If the provided + * {@link java.io.File} is a regular file, then the file's length is returned. + * If the argument is a directory, then the size of the directory is + * calculated recursively. If a directory or subdirectory is security + * restricted, its size will not be included. + * + * @param file the regular file or directory to return the size + * of (must not be {@code null}). + * @return the length of the file, or recursive size of the directory, + * provided (in bytes). + * @throws NullPointerException if the file is {@code null} + * @throws IllegalArgumentException if the file does not exist. + * @since 2.0 + */ + public static long sizeOf(File file) { + + if (!file.exists()) { + String message = file + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (file.isDirectory()) { + return sizeOfDirectory(file); + } else { + return file.length(); + } + + } + + /** + * Returns the size of the specified file or directory. If the provided + * {@link java.io.File} is a regular file, then the file's length is returned. + * If the argument is a directory, then the size of the directory is + * calculated recursively. If a directory or subdirectory is security + * restricted, its size will not be included. + * + * @param file the regular file or directory to return the size + * of (must not be {@code null}). + * @return the length of the file, or recursive size of the directory, + * provided (in bytes). + * @throws NullPointerException if the file is {@code null} + * @throws IllegalArgumentException if the file does not exist. + * @since 2.4 + */ + public static BigInteger sizeOfAsBigInteger(File file) { + + if (!file.exists()) { + String message = file + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (file.isDirectory()) { + return sizeOfDirectoryAsBigInteger(file); + } else { + return BigInteger.valueOf(file.length()); + } + + } + + /** + * Counts the size of a directory recursively (sum of the length of all files). + * + * @param directory directory to inspect, must not be {@code null} + * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total + * is greater than {@link Long#MAX_VALUE}. + * @throws NullPointerException if the directory is {@code null} + */ + public static long sizeOfDirectory(File directory) { + checkDirectory(directory); + + final File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + return 0L; + } + long size = 0; + + for (final File file : files) { + try { + if (!isSymlink(file)) { + size += sizeOf(file); + if (size < 0) { + break; + } + } + } catch (IOException ioe) { + // Ignore exceptions caught when asking if a File is a symlink. + } + } + + return size; + } + + /** + * Counts the size of a directory recursively (sum of the length of all files). + * + * @param directory directory to inspect, must not be {@code null} + * @return size of directory in bytes, 0 if directory is security restricted. + * @throws NullPointerException if the directory is {@code null} + * @since 2.4 + */ + public static BigInteger sizeOfDirectoryAsBigInteger(File directory) { + checkDirectory(directory); + + final File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + return BigInteger.ZERO; + } + BigInteger size = BigInteger.ZERO; + + for (final File file : files) { + try { + if (!isSymlink(file)) { + size = size.add(BigInteger.valueOf(sizeOf(file))); + } + } catch (IOException ioe) { + // Ignore exceptions caught when asking if a File is a symlink. + } + } + + return size; + } + + /** + * Checks that the given {@code File} exists and is a directory. + * + * @param directory The {@code File} to check. + * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory. + */ + private static void checkDirectory(File directory) { + if (!directory.exists()) { + throw new IllegalArgumentException(directory + " does not exist"); + } + if (!directory.isDirectory()) { + throw new IllegalArgumentException(directory + " is not a directory"); + } + } + + //----------------------------------------------------------------------- + + /** + * Tests if the specified File is newer than the reference + * File. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param reference the File of which the modification date + * is used, must not be {@code null} + * @return true if the File exists and has been modified more + * recently than the reference File + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist + */ + public static boolean isFileNewer(File file, File reference) { + if (reference == null) { + throw new IllegalArgumentException("No specified reference file"); + } + if (!reference.exists()) { + throw new IllegalArgumentException("The reference file '" + + reference + "' doesn't exist"); + } + return isFileNewer(file, reference.lastModified()); + } + + /** + * Tests if the specified File is newer than the specified + * Date. + * + * @param file the File of which the modification date + * must be compared, must not be {@code null} + * @param date the date reference, must not be {@code null} + * @return true if the File exists and has been modified + * after the given Date. + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the date is {@code null} + */ + public static boolean isFileNewer(File file, Date date) { + if (date == null) { + throw new IllegalArgumentException("No specified date"); + } + return isFileNewer(file, date.getTime()); + } + + /** + * Tests if the specified File is newer than the specified + * time reference. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param timeMillis the time reference measured in milliseconds since the + * epoch (00:00:00 GMT, January 1, 1970) + * @return true if the File exists and has been modified after + * the given time reference. + * @throws IllegalArgumentException if the file is {@code null} + */ + public static boolean isFileNewer(File file, long timeMillis) { + if (file == null) { + throw new IllegalArgumentException("No specified file"); + } + if (!file.exists()) { + return false; + } + return file.lastModified() > timeMillis; + } + + + //----------------------------------------------------------------------- + + /** + * Tests if the specified File is older than the reference + * File. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param reference the File of which the modification date + * is used, must not be {@code null} + * @return true if the File exists and has been modified before + * the reference File + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist + */ + public static boolean isFileOlder(File file, File reference) { + if (reference == null) { + throw new IllegalArgumentException("No specified reference file"); + } + if (!reference.exists()) { + throw new IllegalArgumentException("The reference file '" + + reference + "' doesn't exist"); + } + return isFileOlder(file, reference.lastModified()); + } + + /** + * Tests if the specified File is older than the specified + * Date. + * + * @param file the File of which the modification date + * must be compared, must not be {@code null} + * @param date the date reference, must not be {@code null} + * @return true if the File exists and has been modified + * before the given Date. + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the date is {@code null} + */ + public static boolean isFileOlder(File file, Date date) { + if (date == null) { + throw new IllegalArgumentException("No specified date"); + } + return isFileOlder(file, date.getTime()); + } + + /** + * Tests if the specified File is older than the specified + * time reference. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param timeMillis the time reference measured in milliseconds since the + * epoch (00:00:00 GMT, January 1, 1970) + * @return true if the File exists and has been modified before + * the given time reference. + * @throws IllegalArgumentException if the file is {@code null} + */ + public static boolean isFileOlder(File file, long timeMillis) { + if (file == null) { + throw new IllegalArgumentException("No specified file"); + } + if (!file.exists()) { + return false; + } + return file.lastModified() < timeMillis; + } + + //----------------------------------------------------------------------- + + /** + * Moves a directory. + *

+ * When the destination directory is on another file system, do a "copy and delete". + * + * @param srcDir the directory to be moved + * @param destDir the destination directory + * @throws NullPointerException if source or destination is {@code null} + * @throws FileExistsException if the destination directory exists + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveDirectory(File srcDir, File destDir) throws IOException { + if (srcDir == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!srcDir.exists()) { + throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); + } + if (!srcDir.isDirectory()) { + throw new IOException("Source '" + srcDir + "' is not a directory"); + } + if (destDir.exists()) { + throw new FileExistsException("Destination '" + destDir + "' already exists"); + } + boolean rename = srcDir.renameTo(destDir); + if (!rename) { + if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { + throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir); + } + copyDirectory(srcDir, destDir); + deleteDirectory(srcDir); + if (srcDir.exists()) { + throw new IOException("Failed to delete original directory '" + srcDir + + "' after copy to '" + destDir + "'"); + } + } + } + + /** + * Moves a directory to another directory. + * + * @param src the file to be moved + * @param destDir the destination file + * @param createDestDir If {@code true} create the destination directory, + * otherwise if {@code false} throw an IOException + * @throws NullPointerException if source or destination is {@code null} + * @throws FileExistsException if the directory exists in the destination directory + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException { + if (src == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination directory must not be null"); + } + if (!destDir.exists() && createDestDir) { + destDir.mkdirs(); + } + if (!destDir.exists()) { + throw new FileNotFoundException("Destination directory '" + destDir + + "' does not exist [createDestDir=" + createDestDir + "]"); + } + if (!destDir.isDirectory()) { + throw new IOException("Destination '" + destDir + "' is not a directory"); + } + moveDirectory(src, new File(destDir, src.getName())); + + } + + /** + * Moves a file. + *

+ * When the destination file is on another file system, do a "copy and delete". + * + * @param srcFile the file to be moved + * @param destFile the destination file + * @throws NullPointerException if source or destination is {@code null} + * @throws FileExistsException if the destination file exists + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveFile(File srcFile, File destFile) throws IOException { + if (srcFile == null) { + throw new NullPointerException("Source must not be null"); + } + if (destFile == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!srcFile.exists()) { + throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); + } + if (srcFile.isDirectory()) { + throw new IOException("Source '" + srcFile + "' is a directory"); + } + if (destFile.exists()) { + throw new FileExistsException("Destination '" + destFile + "' already exists"); + } + if (destFile.isDirectory()) { + throw new IOException("Destination '" + destFile + "' is a directory"); + } + boolean rename = srcFile.renameTo(destFile); + if (!rename) { + copyFile(srcFile, destFile); + if (!srcFile.delete()) { + FileUtils.deleteQuietly(destFile); + throw new IOException("Failed to delete original file '" + srcFile + + "' after copy to '" + destFile + "'"); + } + } + } + + /** + * Moves a file to a directory. + * + * @param srcFile the file to be moved + * @param destDir the destination file + * @param createDestDir If {@code true} create the destination directory, + * otherwise if {@code false} throw an IOException + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException { + if (srcFile == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination directory must not be null"); + } + if (!destDir.exists() && createDestDir) { + destDir.mkdirs(); + } + if (!destDir.exists()) { + throw new FileNotFoundException("Destination directory '" + destDir + + "' does not exist [createDestDir=" + createDestDir + "]"); + } + if (!destDir.isDirectory()) { + throw new IOException("Destination '" + destDir + "' is not a directory"); + } + moveFile(srcFile, new File(destDir, srcFile.getName())); + } + + /** + * Moves a file or directory to the destination directory. + *

+ * When the destination is on another file system, do a "copy and delete". + * + * @param src the file or directory to be moved + * @param destDir the destination directory + * @param createDestDir If {@code true} create the destination directory, + * otherwise if {@code false} throw an IOException + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException { + if (src == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!src.exists()) { + throw new FileNotFoundException("Source '" + src + "' does not exist"); + } + if (src.isDirectory()) { + moveDirectoryToDirectory(src, destDir, createDestDir); + } else { + moveFileToDirectory(src, destDir, createDestDir); + } + } + + /** + * Determines whether the specified file is a Symbolic Link rather than an actual file. + *

+ * Will not return true if there is a Symbolic Link anywhere in the path, + * only if the specific file is. + *

+ * Note: the current implementation always returns {@code false} if the system + * is detected as Windows using {@link FilenameUtils#isSystemWindows()} + * + * @param file the file to check + * @return true if the file is a Symbolic Link + * @throws java.io.IOException if an IO error occurs while checking the file + * @since 2.0 + */ + public static boolean isSymlink(File file) throws IOException { + if (file == null) { + throw new NullPointerException("File must not be null"); + } + if (FilenameUtils.isSystemWindows()) { + return false; + } + File fileInCanonicalDir = null; + if (file.getParent() == null) { + fileInCanonicalDir = file; + } else { + File canonicalDir = file.getParentFile().getCanonicalFile(); + fileInCanonicalDir = new File(canonicalDir, file.getName()); + } + + if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { + return false; + } else { + return true; + } + } + + /** + * Indicates that a file already exists. + * + * @version $Id: FileExistsException.java 1304052 2012-03-22 20:55:29Z ggregory $ + * @since 2.0 + */ + public static class FileExistsException extends IOException { + + /** + * Defines the serial version UID. + */ + private static final long serialVersionUID = 1L; + + /** + * Default Constructor. + */ + public FileExistsException() { + super(); + } + + /** + * Construct an instance with the specified message. + * + * @param message The error message + */ + public FileExistsException(String message) { + super(message); + } + + /** + * Construct an instance with the specified file. + * + * @param file The file that exists + */ + public FileExistsException(File file) { + super("File " + file + " exists"); + } + + } +} diff --git a/src/com/litesuits/common/io/FilenameUtils.java b/app/src/main/java/com/litesuits/common/io/FilenameUtils.java similarity index 97% rename from src/com/litesuits/common/io/FilenameUtils.java rename to app/src/main/java/com/litesuits/common/io/FilenameUtils.java index e936e5f..63f878f 100644 --- a/src/com/litesuits/common/io/FilenameUtils.java +++ b/app/src/main/java/com/litesuits/common/io/FilenameUtils.java @@ -1,1063 +1,1063 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; - -/** - * General filename and filepath manipulation utilities. - *

- * When dealing with filenames you can hit problems when moving from a Windows - * based development machine to a Unix based production machine. - * This class aims to help avoid those problems. - *

- * NOTE: You may be able to avoid using this class entirely simply by - * using JDK {@link java.io.File File} objects and the two argument constructor - * {@link java.io.File#File(java.io.File, String) File(File,String)}. - *

- * Most methods on this class are designed to work the same on both Unix and Windows. - * Those that don't include 'System', 'Unix' or 'Windows' in their name. - *

- * Most methods recognise both separators (forward and back), and both - * sets of prefixes. See the javadoc of each method for details. - *

- * This class defines six components within a filename - * (example C:\dev\project\file.txt): - *

    - *
  • the prefix - C:\
  • - *
  • the path - dev\project\
  • - *
  • the full path - C:\dev\project\
  • - *
  • the name - file.txt
  • - *
  • the base name - file
  • - *
  • the extension - txt
  • - *
- * Note that this class works best if directory filenames end with a separator. - * If you omit the last separator, it is impossible to determine if the filename - * corresponds to a file or a directory. As a result, we have chosen to say - * it corresponds to a file. - *

- * This class only supports Unix and Windows style names. - * Prefixes are matched as follows: - *

- * Windows:
- * a\b\c.txt           --> ""          --> relative
- * \a\b\c.txt          --> "\"         --> current drive absolute
- * C:a\b\c.txt         --> "C:"        --> drive relative
- * C:\a\b\c.txt        --> "C:\"       --> absolute
- * \\server\a\b\c.txt  --> "\\server\" --> UNC
- *
- * Unix:
- * a/b/c.txt           --> ""          --> relative
- * /a/b/c.txt          --> "/"         --> absolute
- * ~/a/b/c.txt         --> "~/"        --> current user
- * ~                   --> "~/"        --> current user (slash added)
- * ~user/a/b/c.txt     --> "~user/"    --> named user
- * ~user               --> "~user/"    --> named user (slash added)
- * 
- * Both prefix styles are matched always, irrespective of the machine that you are - * currently running on. - *

- * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. - * - * @version $Id: FilenameUtils.java 1307462 2012-03-30 15:13:11Z ggregory $ - * @since 1.1 - */ -public class FilenameUtils { - - /** - * The extension separator character. - * @since 1.4 - */ - public static final char EXTENSION_SEPARATOR = '.'; - - /** - * The extension separator String. - * @since 1.4 - */ - public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); - - /** - * The Unix separator character. - */ - private static final char UNIX_SEPARATOR = '/'; - - /** - * The Windows separator character. - */ - private static final char WINDOWS_SEPARATOR = '\\'; - - /** - * The system separator character. - */ - private static final char SYSTEM_SEPARATOR = File.separatorChar; - - /** - * The separator character that is the opposite of the system separator. - */ - private static final char OTHER_SEPARATOR; - static { - if (isSystemWindows()) { - OTHER_SEPARATOR = UNIX_SEPARATOR; - } else { - OTHER_SEPARATOR = WINDOWS_SEPARATOR; - } - } - - /** - * Instances should NOT be constructed in standard programming. - */ - public FilenameUtils() { - super(); - } - - //----------------------------------------------------------------------- - /** - * Determines if Windows file system is in use. - * - * @return true if the system is Windows - */ - static boolean isSystemWindows() { - return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR; - } - - //----------------------------------------------------------------------- - /** - * Checks if the character is a separator. - * - * @param ch the character to check - * @return true if it is a separator character - */ - private static boolean isSeparator(char ch) { - return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; - } - - //----------------------------------------------------------------------- - /** - * Normalizes a path, removing double and single dot path steps. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format of the system. - *

- * A trailing slash will be retained. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo//               -->   /foo/
-     * /foo/./              -->   /foo/
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar/
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo/
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar/
-     * ~/../bar             -->   null
-     * 
- * (Note the file separator returned will be correct for Windows/Unix) - * - * @param filename the filename to normalize, null returns null - * @return the normalized filename, or null if invalid - */ - public static String normalize(String filename) { - return doNormalize(filename, SYSTEM_SEPARATOR, true); - } - /** - * Normalizes a path, removing double and single dot path steps. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format specified. - *

- * A trailing slash will be retained. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo//               -->   /foo/
-     * /foo/./              -->   /foo/
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar/
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo/
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar/
-     * ~/../bar             -->   null
-     * 
- * The output will be the same on both Unix and Windows including - * the separator character. - * - * @param filename the filename to normalize, null returns null - * @param unixSeparator {@code true} if a unix separator should - * be used or {@code false} if a windows separator should be used. - * @return the normalized filename, or null if invalid - * @since 2.0 - */ - public static String normalize(String filename, boolean unixSeparator) { - char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; - return doNormalize(filename, separator, true); - } - - //----------------------------------------------------------------------- - /** - * Normalizes a path, removing double and single dot path steps, - * and removing any final directory separator. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format of the system. - *

- * A trailing slash will be removed. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo//               -->   /foo
-     * /foo/./              -->   /foo
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar
-     * ~/../bar             -->   null
-     * 
- * (Note the file separator returned will be correct for Windows/Unix) - * - * @param filename the filename to normalize, null returns null - * @return the normalized filename, or null if invalid - */ - public static String normalizeNoEndSeparator(String filename) { - return doNormalize(filename, SYSTEM_SEPARATOR, false); - } - - /** - * Normalizes a path, removing double and single dot path steps, - * and removing any final directory separator. - *

- * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format specified. - *

- * A trailing slash will be removed. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

- * The output will be the same on both Unix and Windows including - * the separator character. - *

-     * /foo//               -->   /foo
-     * /foo/./              -->   /foo
-     * /foo/../bar          -->   /bar
-     * /foo/../bar/         -->   /bar
-     * /foo/../bar/../baz   -->   /baz
-     * //foo//./bar         -->   /foo/bar
-     * /../                 -->   null
-     * ../foo               -->   null
-     * foo/bar/..           -->   foo
-     * foo/../../bar        -->   null
-     * foo/../bar           -->   bar
-     * //server/foo/../bar  -->   //server/bar
-     * //server/../bar      -->   null
-     * C:\foo\..\bar        -->   C:\bar
-     * C:\..\bar            -->   null
-     * ~/foo/../bar/        -->   ~/bar
-     * ~/../bar             -->   null
-     * 
- * - * @param filename the filename to normalize, null returns null - * @param unixSeparator {@code true} if a unix separator should - * be used or {@code false} if a windows separtor should be used. - * @return the normalized filename, or null if invalid - * @since 2.0 - */ - public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) { - char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; - return doNormalize(filename, separator, false); - } - - /** - * Internal method to perform the normalization. - * - * @param filename the filename - * @param separator The separator character to use - * @param keepSeparator true to keep the final separator - * @return the normalized filename - */ - private static String doNormalize(String filename, char separator, boolean keepSeparator) { - if (filename == null) { - return null; - } - int size = filename.length(); - if (size == 0) { - return filename; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - - char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy - filename.getChars(0, filename.length(), array, 0); - - // fix separators throughout - char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; - for (int i = 0; i < array.length; i++) { - if (array[i] == otherSeparator) { - array[i] = separator; - } - } - - // add extra separator on the end to simplify code below - boolean lastIsDirectory = true; - if (array[size - 1] != separator) { - array[size++] = separator; - lastIsDirectory = false; - } - - // adjoining slashes - for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == separator) { - System.arraycopy(array, i, array, i - 1, size - i); - size--; - i--; - } - } - - // dot slash - for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && - (i == prefix + 1 || array[i - 2] == separator)) { - if (i == size - 1) { - lastIsDirectory = true; - } - System.arraycopy(array, i + 1, array, i - 1, size - i); - size -=2; - i--; - } - } - - // double dot slash - outer: - for (int i = prefix + 2; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && - (i == prefix + 2 || array[i - 3] == separator)) { - if (i == prefix + 2) { - return null; - } - if (i == size - 1) { - lastIsDirectory = true; - } - int j; - for (j = i - 4 ; j >= prefix; j--) { - if (array[j] == separator) { - // remove b/../ from a/b/../c - System.arraycopy(array, i + 1, array, j + 1, size - i); - size -= i - j; - i = j + 1; - continue outer; - } - } - // remove a/../ from a/../c - System.arraycopy(array, i + 1, array, prefix, size - i); - size -= i + 1 - prefix; - i = prefix + 1; - } - } - - if (size <= 0) { // should never be less than 0 - return ""; - } - if (size <= prefix) { // should never be less than prefix - return new String(array, 0, size); - } - if (lastIsDirectory && keepSeparator) { - return new String(array, 0, size); // keep trailing separator - } - return new String(array, 0, size - 1); // lose trailing separator - } - - //----------------------------------------------------------------------- - /** - * Concatenates a filename to a base path using normal command line style rules. - *

- * The effect is equivalent to resultant directory after changing - * directory to the first argument, followed by changing directory to - * the second argument. - *

- * The first argument is the base path, the second is the path to concatenate. - * The returned path is always normalized via {@link #normalize(String)}, - * thus .. is handled. - *

- * If pathToAdd is absolute (has an absolute prefix), then - * it will be normalized and returned. - * Otherwise, the paths will be joined, normalized and returned. - *

- * The output will be the same on both Unix and Windows except - * for the separator character. - *

-     * /foo/ + bar          -->   /foo/bar
-     * /foo + bar           -->   /foo/bar
-     * /foo + /bar          -->   /bar
-     * /foo + C:/bar        -->   C:/bar
-     * /foo + C:bar         -->   C:bar (*)
-     * /foo/a/ + ../bar     -->   foo/bar
-     * /foo/ + ../../bar    -->   null
-     * /foo/ + /bar         -->   /bar
-     * /foo/.. + /bar       -->   /bar
-     * /foo + bar/c.txt     -->   /foo/bar/c.txt
-     * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)
-     * 
- * (*) Note that the Windows relative drive prefix is unreliable when - * used with this method. - * (!) Note that the first parameter must be a path. If it ends with a name, then - * the name will be built into the concatenated path. If this might be a problem, - * use {@link #getFullPath(String)} on the base path argument. - * - * @param basePath the base path to attach to, always treated as a path - * @param fullFilenameToAdd the filename (or path) to attach to the base - * @return the concatenated path, or null if invalid - */ - public static String concat(String basePath, String fullFilenameToAdd) { - int prefix = getPrefixLength(fullFilenameToAdd); - if (prefix < 0) { - return null; - } - if (prefix > 0) { - return normalize(fullFilenameToAdd); - } - if (basePath == null) { - return null; - } - int len = basePath.length(); - if (len == 0) { - return normalize(fullFilenameToAdd); - } - char ch = basePath.charAt(len - 1); - if (isSeparator(ch)) { - return normalize(basePath + fullFilenameToAdd); - } else { - return normalize(basePath + '/' + fullFilenameToAdd); - } - } - - //----------------------------------------------------------------------- - /** - * Converts all separators to the Unix separator of forward slash. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToUnix(String path) { - if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) { - return path; - } - return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); - } - - /** - * Converts all separators to the Windows separator of backslash. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToWindows(String path) { - if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) { - return path; - } - return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR); - } - - /** - * Converts all separators to the system separator. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToSystem(String path) { - if (path == null) { - return null; - } - if (isSystemWindows()) { - return separatorsToWindows(path); - } else { - return separatorsToUnix(path); - } - } - - //----------------------------------------------------------------------- - /** - * Returns the length of the filename prefix, such as C:/ or ~/. - *

- * This method will handle a file in either Unix or Windows format. - *

- * The prefix length includes the first slash in the full filename - * if applicable. Thus, it is possible that the length returned is greater - * than the length of the input string. - *

-     * Windows:
-     * a\b\c.txt           --> ""          --> relative
-     * \a\b\c.txt          --> "\"         --> current drive absolute
-     * C:a\b\c.txt         --> "C:"        --> drive relative
-     * C:\a\b\c.txt        --> "C:\"       --> absolute
-     * \\server\a\b\c.txt  --> "\\server\" --> UNC
-     *
-     * Unix:
-     * a/b/c.txt           --> ""          --> relative
-     * /a/b/c.txt          --> "/"         --> absolute
-     * ~/a/b/c.txt         --> "~/"        --> current user
-     * ~                   --> "~/"        --> current user (slash added)
-     * ~user/a/b/c.txt     --> "~user/"    --> named user
-     * ~user               --> "~user/"    --> named user (slash added)
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * ie. both Unix and Windows prefixes are matched regardless. - * - * @param filename the filename to find the prefix in, null returns -1 - * @return the length of the prefix, -1 if invalid or null - */ - public static int getPrefixLength(String filename) { - if (filename == null) { - return -1; - } - int len = filename.length(); - if (len == 0) { - return 0; - } - char ch0 = filename.charAt(0); - if (ch0 == ':') { - return -1; - } - if (len == 1) { - if (ch0 == '~') { - return 2; // return a length greater than the input - } - return isSeparator(ch0) ? 1 : 0; - } else { - if (ch0 == '~') { - int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); - int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); - if (posUnix == -1 && posWin == -1) { - return len + 1; // return a length greater than the input - } - posUnix = posUnix == -1 ? posWin : posUnix; - posWin = posWin == -1 ? posUnix : posWin; - return Math.min(posUnix, posWin) + 1; - } - char ch1 = filename.charAt(1); - if (ch1 == ':') { - ch0 = Character.toUpperCase(ch0); - if (ch0 >= 'A' && ch0 <= 'Z') { - if (len == 2 || isSeparator(filename.charAt(2)) == false) { - return 2; - } - return 3; - } - return -1; - - } else if (isSeparator(ch0) && isSeparator(ch1)) { - int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); - int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); - if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) { - return -1; - } - posUnix = posUnix == -1 ? posWin : posUnix; - posWin = posWin == -1 ? posUnix : posWin; - return Math.min(posUnix, posWin) + 1; - } else { - return isSeparator(ch0) ? 1 : 0; - } - } - } - - /** - * Returns the index of the last directory separator character. - *

- * This method will handle a file in either Unix or Windows format. - * The position of the last forward or backslash is returned. - *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to find the last path separator in, null returns -1 - * @return the index of the last separator character, or -1 if there - * is no such character - */ - public static int indexOfLastSeparator(String filename) { - if (filename == null) { - return -1; - } - int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); - int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); - return Math.max(lastUnixPos, lastWindowsPos); - } - - /** - * Returns the index of the last extension separator character, which is a dot. - *

- * This method also checks that there is no directory separator after the last dot. - * To do this it uses {@link #indexOfLastSeparator(String)} which will - * handle a file in either Unix or Windows format. - *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to find the last path separator in, null returns -1 - * @return the index of the last separator character, or -1 if there - * is no such character - */ - public static int indexOfExtension(String filename) { - if (filename == null) { - return -1; - } - int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); - int lastSeparator = indexOfLastSeparator(filename); - return lastSeparator > extensionPos ? -1 : extensionPos; - } - - //----------------------------------------------------------------------- - /** - * Gets the prefix from a full filename, such as C:/ - * or ~/. - *

- * This method will handle a file in either Unix or Windows format. - * The prefix includes the first slash in the full filename where applicable. - *

-     * Windows:
-     * a\b\c.txt           --> ""          --> relative
-     * \a\b\c.txt          --> "\"         --> current drive absolute
-     * C:a\b\c.txt         --> "C:"        --> drive relative
-     * C:\a\b\c.txt        --> "C:\"       --> absolute
-     * \\server\a\b\c.txt  --> "\\server\" --> UNC
-     *
-     * Unix:
-     * a/b/c.txt           --> ""          --> relative
-     * /a/b/c.txt          --> "/"         --> absolute
-     * ~/a/b/c.txt         --> "~/"        --> current user
-     * ~                   --> "~/"        --> current user (slash added)
-     * ~user/a/b/c.txt     --> "~user/"    --> named user
-     * ~user               --> "~user/"    --> named user (slash added)
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * ie. both Unix and Windows prefixes are matched regardless. - * - * @param filename the filename to query, null returns null - * @return the prefix of the file, null if invalid - */ - public static String getPrefix(String filename) { - if (filename == null) { - return null; - } - int len = getPrefixLength(filename); - if (len < 0) { - return null; - } - if (len > filename.length()) { - return filename + UNIX_SEPARATOR; // we know this only happens for unix - } - return filename.substring(0, len); - } - - /** - * Gets the path from a full filename, which excludes the prefix. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before and - * including the last forward or backslash. - *

-     * C:\a\b\c.txt --> a\b\
-     * ~/a/b/c.txt  --> a/b/
-     * a.txt        --> ""
-     * a/b/c        --> a/b/
-     * a/b/c/       --> a/b/c/
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - *

- * This method drops the prefix from the result. - * See {@link #getFullPath(String)} for the method that retains the prefix. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getPath(String filename) { - return doGetPath(filename, 1); - } - - /** - * Gets the path from a full filename, which excludes the prefix, and - * also excluding the final directory separator. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before the - * last forward or backslash. - *

-     * C:\a\b\c.txt --> a\b
-     * ~/a/b/c.txt  --> a/b
-     * a.txt        --> ""
-     * a/b/c        --> a/b
-     * a/b/c/       --> a/b/c
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - *

- * This method drops the prefix from the result. - * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getPathNoEndSeparator(String filename) { - return doGetPath(filename, 0); - } - - /** - * Does the work of getting the path. - * - * @param filename the filename - * @param separatorAdd 0 to omit the end separator, 1 to return it - * @return the path - */ - private static String doGetPath(String filename, int separatorAdd) { - if (filename == null) { - return null; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - int index = indexOfLastSeparator(filename); - int endIndex = index+separatorAdd; - if (prefix >= filename.length() || index < 0 || prefix >= endIndex) { - return ""; - } - return filename.substring(prefix, endIndex); - } - - /** - * Gets the full path from a full filename, which is the prefix + path. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before and - * including the last forward or backslash. - *

-     * C:\a\b\c.txt --> C:\a\b\
-     * ~/a/b/c.txt  --> ~/a/b/
-     * a.txt        --> ""
-     * a/b/c        --> a/b/
-     * a/b/c/       --> a/b/c/
-     * C:           --> C:
-     * C:\          --> C:\
-     * ~            --> ~/
-     * ~/           --> ~/
-     * ~user        --> ~user/
-     * ~user/       --> ~user/
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getFullPath(String filename) { - return doGetFullPath(filename, true); - } - - /** - * Gets the full path from a full filename, which is the prefix + path, - * and also excluding the final directory separator. - *

- * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before the - * last forward or backslash. - *

-     * C:\a\b\c.txt --> C:\a\b
-     * ~/a/b/c.txt  --> ~/a/b
-     * a.txt        --> ""
-     * a/b/c        --> a/b
-     * a/b/c/       --> a/b/c
-     * C:           --> C:
-     * C:\          --> C:\
-     * ~            --> ~
-     * ~/           --> ~
-     * ~user        --> ~user
-     * ~user/       --> ~user
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getFullPathNoEndSeparator(String filename) { - return doGetFullPath(filename, false); - } - - /** - * Does the work of getting the path. - * - * @param filename the filename - * @param includeSeparator true to include the end separator - * @return the path - */ - private static String doGetFullPath(String filename, boolean includeSeparator) { - if (filename == null) { - return null; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - if (prefix >= filename.length()) { - if (includeSeparator) { - return getPrefix(filename); // add end slash if necessary - } else { - return filename; - } - } - int index = indexOfLastSeparator(filename); - if (index < 0) { - return filename.substring(0, prefix); - } - int end = index + (includeSeparator ? 1 : 0); - if (end == 0) { - end++; - } - return filename.substring(0, end); - } - - /** - * Gets the name minus the path from a full filename. - *

- * This method will handle a file in either Unix or Windows format. - * The text after the last forward or backslash is returned. - *

-     * a/b/c.txt --> c.txt
-     * a.txt     --> a.txt
-     * a/b/c     --> c
-     * a/b/c/    --> ""
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the name of the file without the path, or an empty string if none exists - */ - public static String getName(String filename) { - if (filename == null) { - return null; - } - int index = indexOfLastSeparator(filename); - return filename.substring(index + 1); - } - - /** - * Gets the extension of a filename. - *

- * This method returns the textual part of the filename after the last dot. - * There must be no directory separator after the dot. - *

-     * foo.txt      --> "txt"
-     * a/b/c.jpg    --> "jpg"
-     * a/b.txt/c    --> ""
-     * a/b/c        --> ""
-     * 
- *

- * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to retrieve the extension of. - * @return the extension of the file or an empty string if none exists or {@code null} - * if the filename is {@code null}. - */ - public static String getExtension(String filename) { - if (filename == null) { - return null; - } - int index = indexOfExtension(filename); - if (index == -1) { - return ""; - } else { - return filename.substring(index + 1); - } - } - - //----------------------------------------------------------------------- - /** - * Checks whether the extension of the filename is that specified. - *

- * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extension the extension to check for, null or empty checks for no extension - * @return true if the filename has the specified extension - */ - public static boolean isExtension(String filename, String extension) { - if (filename == null) { - return false; - } - if (extension == null || extension.length() == 0) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - return fileExt.equals(extension); - } - - /** - * Checks whether the extension of the filename is one of those specified. - *

- * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the filename is one of the extensions - */ - public static boolean isExtension(String filename, String[] extensions) { - if (filename == null) { - return false; - } - if (extensions == null || extensions.length == 0) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - for (String extension : extensions) { - if (fileExt.equals(extension)) { - return true; - } - } - return false; - } - - /** - * Checks whether the extension of the filename is one of those specified. - *

- * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the filename is one of the extensions - */ - public static boolean isExtension(String filename, Collection extensions) { - if (filename == null) { - return false; - } - if (extensions == null || extensions.isEmpty()) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - for (String extension : extensions) { - if (fileExt.equals(extension)) { - return true; - } - } - return false; - } - - //----------------------------------------------------------------------- - - /** - * Splits a string into a number of tokens. - * The text is split by '?' and '*'. - * Where multiple '*' occur consecutively they are collapsed into a single '*'. - * - * @param text the text to split - * @return the array of tokens, never null - */ - static String[] splitOnTokens(String text) { - // used by wildcardMatch - // package level so a unit test may run on this - - if (text.indexOf('?') == -1 && text.indexOf('*') == -1) { - return new String[] { text }; - } - - char[] array = text.toCharArray(); - ArrayList list = new ArrayList(); - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < array.length; i++) { - if (array[i] == '?' || array[i] == '*') { - if (buffer.length() != 0) { - list.add(buffer.toString()); - buffer.setLength(0); - } - if (array[i] == '?') { - list.add("?"); - } else if (list.isEmpty() || - i > 0 && list.get(list.size() - 1).equals("*") == false) { - list.add("*"); - } - } else { - buffer.append(array[i]); - } - } - if (buffer.length() != 0) { - list.add(buffer.toString()); - } - - return list.toArray( new String[ list.size() ] ); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; + +/** + * General filename and filepath manipulation utilities. + *

+ * When dealing with filenames you can hit problems when moving from a Windows + * based development machine to a Unix based production machine. + * This class aims to help avoid those problems. + *

+ * NOTE: You may be able to avoid using this class entirely simply by + * using JDK {@link java.io.File File} objects and the two argument constructor + * {@link java.io.File#File(java.io.File, String) File(File,String)}. + *

+ * Most methods on this class are designed to work the same on both Unix and Windows. + * Those that don't include 'System', 'Unix' or 'Windows' in their name. + *

+ * Most methods recognise both separators (forward and back), and both + * sets of prefixes. See the javadoc of each method for details. + *

+ * This class defines six components within a filename + * (example C:\dev\project\file.txt): + *

    + *
  • the prefix - C:\
  • + *
  • the path - dev\project\
  • + *
  • the full path - C:\dev\project\
  • + *
  • the name - file.txt
  • + *
  • the base name - file
  • + *
  • the extension - txt
  • + *
+ * Note that this class works best if directory filenames end with a separator. + * If you omit the last separator, it is impossible to determine if the filename + * corresponds to a file or a directory. As a result, we have chosen to say + * it corresponds to a file. + *

+ * This class only supports Unix and Windows style names. + * Prefixes are matched as follows: + *

+ * Windows:
+ * a\b\c.txt           --> ""          --> relative
+ * \a\b\c.txt          --> "\"         --> current drive absolute
+ * C:a\b\c.txt         --> "C:"        --> drive relative
+ * C:\a\b\c.txt        --> "C:\"       --> absolute
+ * \\server\a\b\c.txt  --> "\\server\" --> UNC
+ *
+ * Unix:
+ * a/b/c.txt           --> ""          --> relative
+ * /a/b/c.txt          --> "/"         --> absolute
+ * ~/a/b/c.txt         --> "~/"        --> current user
+ * ~                   --> "~/"        --> current user (slash added)
+ * ~user/a/b/c.txt     --> "~user/"    --> named user
+ * ~user               --> "~user/"    --> named user (slash added)
+ * 
+ * Both prefix styles are matched always, irrespective of the machine that you are + * currently running on. + *

+ * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. + * + * @version $Id: FilenameUtils.java 1307462 2012-03-30 15:13:11Z ggregory $ + * @since 1.1 + */ +public class FilenameUtils { + + /** + * The extension separator character. + * @since 1.4 + */ + public static final char EXTENSION_SEPARATOR = '.'; + + /** + * The extension separator String. + * @since 1.4 + */ + public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); + + /** + * The Unix separator character. + */ + private static final char UNIX_SEPARATOR = '/'; + + /** + * The Windows separator character. + */ + private static final char WINDOWS_SEPARATOR = '\\'; + + /** + * The system separator character. + */ + private static final char SYSTEM_SEPARATOR = File.separatorChar; + + /** + * The separator character that is the opposite of the system separator. + */ + private static final char OTHER_SEPARATOR; + static { + if (isSystemWindows()) { + OTHER_SEPARATOR = UNIX_SEPARATOR; + } else { + OTHER_SEPARATOR = WINDOWS_SEPARATOR; + } + } + + /** + * Instances should NOT be constructed in standard programming. + */ + public FilenameUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Determines if Windows file system is in use. + * + * @return true if the system is Windows + */ + static boolean isSystemWindows() { + return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR; + } + + //----------------------------------------------------------------------- + /** + * Checks if the character is a separator. + * + * @param ch the character to check + * @return true if it is a separator character + */ + private static boolean isSeparator(char ch) { + return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; + } + + //----------------------------------------------------------------------- + /** + * Normalizes a path, removing double and single dot path steps. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

+ * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo/
+     * /foo/./              -->   /foo/
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar/
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo/
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar/
+     * ~/../bar             -->   null
+     * 
+ * (Note the file separator returned will be correct for Windows/Unix) + * + * @param filename the filename to normalize, null returns null + * @return the normalized filename, or null if invalid + */ + public static String normalize(String filename) { + return doNormalize(filename, SYSTEM_SEPARATOR, true); + } + /** + * Normalizes a path, removing double and single dot path steps. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format specified. + *

+ * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo/
+     * /foo/./              -->   /foo/
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar/
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo/
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar/
+     * ~/../bar             -->   null
+     * 
+ * The output will be the same on both Unix and Windows including + * the separator character. + * + * @param filename the filename to normalize, null returns null + * @param unixSeparator {@code true} if a unix separator should + * be used or {@code false} if a windows separator should be used. + * @return the normalized filename, or null if invalid + * @since 2.0 + */ + public static String normalize(String filename, boolean unixSeparator) { + char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; + return doNormalize(filename, separator, true); + } + + //----------------------------------------------------------------------- + /** + * Normalizes a path, removing double and single dot path steps, + * and removing any final directory separator. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

+ * A trailing slash will be removed. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo//               -->   /foo
+     * /foo/./              -->   /foo
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar
+     * ~/../bar             -->   null
+     * 
+ * (Note the file separator returned will be correct for Windows/Unix) + * + * @param filename the filename to normalize, null returns null + * @return the normalized filename, or null if invalid + */ + public static String normalizeNoEndSeparator(String filename) { + return doNormalize(filename, SYSTEM_SEPARATOR, false); + } + + /** + * Normalizes a path, removing double and single dot path steps, + * and removing any final directory separator. + *

+ * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format specified. + *

+ * A trailing slash will be removed. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

+ * The output will be the same on both Unix and Windows including + * the separator character. + *

+     * /foo//               -->   /foo
+     * /foo/./              -->   /foo
+     * /foo/../bar          -->   /bar
+     * /foo/../bar/         -->   /bar
+     * /foo/../bar/../baz   -->   /baz
+     * //foo//./bar         -->   /foo/bar
+     * /../                 -->   null
+     * ../foo               -->   null
+     * foo/bar/..           -->   foo
+     * foo/../../bar        -->   null
+     * foo/../bar           -->   bar
+     * //server/foo/../bar  -->   //server/bar
+     * //server/../bar      -->   null
+     * C:\foo\..\bar        -->   C:\bar
+     * C:\..\bar            -->   null
+     * ~/foo/../bar/        -->   ~/bar
+     * ~/../bar             -->   null
+     * 
+ * + * @param filename the filename to normalize, null returns null + * @param unixSeparator {@code true} if a unix separator should + * be used or {@code false} if a windows separtor should be used. + * @return the normalized filename, or null if invalid + * @since 2.0 + */ + public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) { + char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; + return doNormalize(filename, separator, false); + } + + /** + * Internal method to perform the normalization. + * + * @param filename the filename + * @param separator The separator character to use + * @param keepSeparator true to keep the final separator + * @return the normalized filename + */ + private static String doNormalize(String filename, char separator, boolean keepSeparator) { + if (filename == null) { + return null; + } + int size = filename.length(); + if (size == 0) { + return filename; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + + char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy + filename.getChars(0, filename.length(), array, 0); + + // fix separators throughout + char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; + for (int i = 0; i < array.length; i++) { + if (array[i] == otherSeparator) { + array[i] = separator; + } + } + + // add extra separator on the end to simplify code below + boolean lastIsDirectory = true; + if (array[size - 1] != separator) { + array[size++] = separator; + lastIsDirectory = false; + } + + // adjoining slashes + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == separator) { + System.arraycopy(array, i, array, i - 1, size - i); + size--; + i--; + } + } + + // dot slash + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && + (i == prefix + 1 || array[i - 2] == separator)) { + if (i == size - 1) { + lastIsDirectory = true; + } + System.arraycopy(array, i + 1, array, i - 1, size - i); + size -=2; + i--; + } + } + + // double dot slash + outer: + for (int i = prefix + 2; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && + (i == prefix + 2 || array[i - 3] == separator)) { + if (i == prefix + 2) { + return null; + } + if (i == size - 1) { + lastIsDirectory = true; + } + int j; + for (j = i - 4 ; j >= prefix; j--) { + if (array[j] == separator) { + // remove b/../ from a/b/../c + System.arraycopy(array, i + 1, array, j + 1, size - i); + size -= i - j; + i = j + 1; + continue outer; + } + } + // remove a/../ from a/../c + System.arraycopy(array, i + 1, array, prefix, size - i); + size -= i + 1 - prefix; + i = prefix + 1; + } + } + + if (size <= 0) { // should never be less than 0 + return ""; + } + if (size <= prefix) { // should never be less than prefix + return new String(array, 0, size); + } + if (lastIsDirectory && keepSeparator) { + return new String(array, 0, size); // keep trailing separator + } + return new String(array, 0, size - 1); // lose trailing separator + } + + //----------------------------------------------------------------------- + /** + * Concatenates a filename to a base path using normal command line style rules. + *

+ * The effect is equivalent to resultant directory after changing + * directory to the first argument, followed by changing directory to + * the second argument. + *

+ * The first argument is the base path, the second is the path to concatenate. + * The returned path is always normalized via {@link #normalize(String)}, + * thus .. is handled. + *

+ * If pathToAdd is absolute (has an absolute prefix), then + * it will be normalized and returned. + * Otherwise, the paths will be joined, normalized and returned. + *

+ * The output will be the same on both Unix and Windows except + * for the separator character. + *

+     * /foo/ + bar          -->   /foo/bar
+     * /foo + bar           -->   /foo/bar
+     * /foo + /bar          -->   /bar
+     * /foo + C:/bar        -->   C:/bar
+     * /foo + C:bar         -->   C:bar (*)
+     * /foo/a/ + ../bar     -->   foo/bar
+     * /foo/ + ../../bar    -->   null
+     * /foo/ + /bar         -->   /bar
+     * /foo/.. + /bar       -->   /bar
+     * /foo + bar/c.txt     -->   /foo/bar/c.txt
+     * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)
+     * 
+ * (*) Note that the Windows relative drive prefix is unreliable when + * used with this method. + * (!) Note that the first parameter must be a path. If it ends with a name, then + * the name will be built into the concatenated path. If this might be a problem, + * use {@link #getFullPath(String)} on the base path argument. + * + * @param basePath the base path to attach to, always treated as a path + * @param fullFilenameToAdd the filename (or path) to attach to the base + * @return the concatenated path, or null if invalid + */ + public static String concat(String basePath, String fullFilenameToAdd) { + int prefix = getPrefixLength(fullFilenameToAdd); + if (prefix < 0) { + return null; + } + if (prefix > 0) { + return normalize(fullFilenameToAdd); + } + if (basePath == null) { + return null; + } + int len = basePath.length(); + if (len == 0) { + return normalize(fullFilenameToAdd); + } + char ch = basePath.charAt(len - 1); + if (isSeparator(ch)) { + return normalize(basePath + fullFilenameToAdd); + } else { + return normalize(basePath + '/' + fullFilenameToAdd); + } + } + + //----------------------------------------------------------------------- + /** + * Converts all separators to the Unix separator of forward slash. + * + * @param path the path to be changed, null ignored + * @return the updated path + */ + public static String separatorsToUnix(String path) { + if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) { + return path; + } + return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); + } + + /** + * Converts all separators to the Windows separator of backslash. + * + * @param path the path to be changed, null ignored + * @return the updated path + */ + public static String separatorsToWindows(String path) { + if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) { + return path; + } + return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR); + } + + /** + * Converts all separators to the system separator. + * + * @param path the path to be changed, null ignored + * @return the updated path + */ + public static String separatorsToSystem(String path) { + if (path == null) { + return null; + } + if (isSystemWindows()) { + return separatorsToWindows(path); + } else { + return separatorsToUnix(path); + } + } + + //----------------------------------------------------------------------- + /** + * Returns the length of the filename prefix, such as C:/ or ~/. + *

+ * This method will handle a file in either Unix or Windows format. + *

+ * The prefix length includes the first slash in the full filename + * if applicable. Thus, it is possible that the length returned is greater + * than the length of the input string. + *

+     * Windows:
+     * a\b\c.txt           --> ""          --> relative
+     * \a\b\c.txt          --> "\"         --> current drive absolute
+     * C:a\b\c.txt         --> "C:"        --> drive relative
+     * C:\a\b\c.txt        --> "C:\"       --> absolute
+     * \\server\a\b\c.txt  --> "\\server\" --> UNC
+     *
+     * Unix:
+     * a/b/c.txt           --> ""          --> relative
+     * /a/b/c.txt          --> "/"         --> absolute
+     * ~/a/b/c.txt         --> "~/"        --> current user
+     * ~                   --> "~/"        --> current user (slash added)
+     * ~user/a/b/c.txt     --> "~user/"    --> named user
+     * ~user               --> "~user/"    --> named user (slash added)
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * ie. both Unix and Windows prefixes are matched regardless. + * + * @param filename the filename to find the prefix in, null returns -1 + * @return the length of the prefix, -1 if invalid or null + */ + public static int getPrefixLength(String filename) { + if (filename == null) { + return -1; + } + int len = filename.length(); + if (len == 0) { + return 0; + } + char ch0 = filename.charAt(0); + if (ch0 == ':') { + return -1; + } + if (len == 1) { + if (ch0 == '~') { + return 2; // return a length greater than the input + } + return isSeparator(ch0) ? 1 : 0; + } else { + if (ch0 == '~') { + int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); + int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); + if (posUnix == -1 && posWin == -1) { + return len + 1; // return a length greater than the input + } + posUnix = posUnix == -1 ? posWin : posUnix; + posWin = posWin == -1 ? posUnix : posWin; + return Math.min(posUnix, posWin) + 1; + } + char ch1 = filename.charAt(1); + if (ch1 == ':') { + ch0 = Character.toUpperCase(ch0); + if (ch0 >= 'A' && ch0 <= 'Z') { + if (len == 2 || isSeparator(filename.charAt(2)) == false) { + return 2; + } + return 3; + } + return -1; + + } else if (isSeparator(ch0) && isSeparator(ch1)) { + int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); + int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); + if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) { + return -1; + } + posUnix = posUnix == -1 ? posWin : posUnix; + posWin = posWin == -1 ? posUnix : posWin; + return Math.min(posUnix, posWin) + 1; + } else { + return isSeparator(ch0) ? 1 : 0; + } + } + } + + /** + * Returns the index of the last directory separator character. + *

+ * This method will handle a file in either Unix or Windows format. + * The position of the last forward or backslash is returned. + *

+ * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to find the last path separator in, null returns -1 + * @return the index of the last separator character, or -1 if there + * is no such character + */ + public static int indexOfLastSeparator(String filename) { + if (filename == null) { + return -1; + } + int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); + int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); + return Math.max(lastUnixPos, lastWindowsPos); + } + + /** + * Returns the index of the last extension separator character, which is a dot. + *

+ * This method also checks that there is no directory separator after the last dot. + * To do this it uses {@link #indexOfLastSeparator(String)} which will + * handle a file in either Unix or Windows format. + *

+ * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to find the last path separator in, null returns -1 + * @return the index of the last separator character, or -1 if there + * is no such character + */ + public static int indexOfExtension(String filename) { + if (filename == null) { + return -1; + } + int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); + int lastSeparator = indexOfLastSeparator(filename); + return lastSeparator > extensionPos ? -1 : extensionPos; + } + + //----------------------------------------------------------------------- + /** + * Gets the prefix from a full filename, such as C:/ + * or ~/. + *

+ * This method will handle a file in either Unix or Windows format. + * The prefix includes the first slash in the full filename where applicable. + *

+     * Windows:
+     * a\b\c.txt           --> ""          --> relative
+     * \a\b\c.txt          --> "\"         --> current drive absolute
+     * C:a\b\c.txt         --> "C:"        --> drive relative
+     * C:\a\b\c.txt        --> "C:\"       --> absolute
+     * \\server\a\b\c.txt  --> "\\server\" --> UNC
+     *
+     * Unix:
+     * a/b/c.txt           --> ""          --> relative
+     * /a/b/c.txt          --> "/"         --> absolute
+     * ~/a/b/c.txt         --> "~/"        --> current user
+     * ~                   --> "~/"        --> current user (slash added)
+     * ~user/a/b/c.txt     --> "~user/"    --> named user
+     * ~user               --> "~user/"    --> named user (slash added)
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * ie. both Unix and Windows prefixes are matched regardless. + * + * @param filename the filename to query, null returns null + * @return the prefix of the file, null if invalid + */ + public static String getPrefix(String filename) { + if (filename == null) { + return null; + } + int len = getPrefixLength(filename); + if (len < 0) { + return null; + } + if (len > filename.length()) { + return filename + UNIX_SEPARATOR; // we know this only happens for unix + } + return filename.substring(0, len); + } + + /** + * Gets the path from a full filename, which excludes the prefix. + *

+ * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before and + * including the last forward or backslash. + *

+     * C:\a\b\c.txt --> a\b\
+     * ~/a/b/c.txt  --> a/b/
+     * a.txt        --> ""
+     * a/b/c        --> a/b/
+     * a/b/c/       --> a/b/c/
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + *

+ * This method drops the prefix from the result. + * See {@link #getFullPath(String)} for the method that retains the prefix. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getPath(String filename) { + return doGetPath(filename, 1); + } + + /** + * Gets the path from a full filename, which excludes the prefix, and + * also excluding the final directory separator. + *

+ * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before the + * last forward or backslash. + *

+     * C:\a\b\c.txt --> a\b
+     * ~/a/b/c.txt  --> a/b
+     * a.txt        --> ""
+     * a/b/c        --> a/b
+     * a/b/c/       --> a/b/c
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + *

+ * This method drops the prefix from the result. + * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getPathNoEndSeparator(String filename) { + return doGetPath(filename, 0); + } + + /** + * Does the work of getting the path. + * + * @param filename the filename + * @param separatorAdd 0 to omit the end separator, 1 to return it + * @return the path + */ + private static String doGetPath(String filename, int separatorAdd) { + if (filename == null) { + return null; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + int index = indexOfLastSeparator(filename); + int endIndex = index+separatorAdd; + if (prefix >= filename.length() || index < 0 || prefix >= endIndex) { + return ""; + } + return filename.substring(prefix, endIndex); + } + + /** + * Gets the full path from a full filename, which is the prefix + path. + *

+ * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before and + * including the last forward or backslash. + *

+     * C:\a\b\c.txt --> C:\a\b\
+     * ~/a/b/c.txt  --> ~/a/b/
+     * a.txt        --> ""
+     * a/b/c        --> a/b/
+     * a/b/c/       --> a/b/c/
+     * C:           --> C:
+     * C:\          --> C:\
+     * ~            --> ~/
+     * ~/           --> ~/
+     * ~user        --> ~user/
+     * ~user/       --> ~user/
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getFullPath(String filename) { + return doGetFullPath(filename, true); + } + + /** + * Gets the full path from a full filename, which is the prefix + path, + * and also excluding the final directory separator. + *

+ * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before the + * last forward or backslash. + *

+     * C:\a\b\c.txt --> C:\a\b
+     * ~/a/b/c.txt  --> ~/a/b
+     * a.txt        --> ""
+     * a/b/c        --> a/b
+     * a/b/c/       --> a/b/c
+     * C:           --> C:
+     * C:\          --> C:\
+     * ~            --> ~
+     * ~/           --> ~
+     * ~user        --> ~user
+     * ~user/       --> ~user
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getFullPathNoEndSeparator(String filename) { + return doGetFullPath(filename, false); + } + + /** + * Does the work of getting the path. + * + * @param filename the filename + * @param includeSeparator true to include the end separator + * @return the path + */ + private static String doGetFullPath(String filename, boolean includeSeparator) { + if (filename == null) { + return null; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + if (prefix >= filename.length()) { + if (includeSeparator) { + return getPrefix(filename); // add end slash if necessary + } else { + return filename; + } + } + int index = indexOfLastSeparator(filename); + if (index < 0) { + return filename.substring(0, prefix); + } + int end = index + (includeSeparator ? 1 : 0); + if (end == 0) { + end++; + } + return filename.substring(0, end); + } + + /** + * Gets the name minus the path from a full filename. + *

+ * This method will handle a file in either Unix or Windows format. + * The text after the last forward or backslash is returned. + *

+     * a/b/c.txt --> c.txt
+     * a.txt     --> a.txt
+     * a/b/c     --> c
+     * a/b/c/    --> ""
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to query, null returns null + * @return the name of the file without the path, or an empty string if none exists + */ + public static String getName(String filename) { + if (filename == null) { + return null; + } + int index = indexOfLastSeparator(filename); + return filename.substring(index + 1); + } + + /** + * Gets the extension of a filename. + *

+ * This method returns the textual part of the filename after the last dot. + * There must be no directory separator after the dot. + *

+     * foo.txt      --> "txt"
+     * a/b/c.jpg    --> "jpg"
+     * a/b.txt/c    --> ""
+     * a/b/c        --> ""
+     * 
+ *

+ * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to retrieve the extension of. + * @return the extension of the file or an empty string if none exists or {@code null} + * if the filename is {@code null}. + */ + public static String getExtension(String filename) { + if (filename == null) { + return null; + } + int index = indexOfExtension(filename); + if (index == -1) { + return ""; + } else { + return filename.substring(index + 1); + } + } + + //----------------------------------------------------------------------- + /** + * Checks whether the extension of the filename is that specified. + *

+ * This method obtains the extension as the textual part of the filename + * after the last dot. There must be no directory separator after the dot. + * The extension check is case-sensitive on all platforms. + * + * @param filename the filename to query, null returns false + * @param extension the extension to check for, null or empty checks for no extension + * @return true if the filename has the specified extension + */ + public static boolean isExtension(String filename, String extension) { + if (filename == null) { + return false; + } + if (extension == null || extension.length() == 0) { + return indexOfExtension(filename) == -1; + } + String fileExt = getExtension(filename); + return fileExt.equals(extension); + } + + /** + * Checks whether the extension of the filename is one of those specified. + *

+ * This method obtains the extension as the textual part of the filename + * after the last dot. There must be no directory separator after the dot. + * The extension check is case-sensitive on all platforms. + * + * @param filename the filename to query, null returns false + * @param extensions the extensions to check for, null checks for no extension + * @return true if the filename is one of the extensions + */ + public static boolean isExtension(String filename, String[] extensions) { + if (filename == null) { + return false; + } + if (extensions == null || extensions.length == 0) { + return indexOfExtension(filename) == -1; + } + String fileExt = getExtension(filename); + for (String extension : extensions) { + if (fileExt.equals(extension)) { + return true; + } + } + return false; + } + + /** + * Checks whether the extension of the filename is one of those specified. + *

+ * This method obtains the extension as the textual part of the filename + * after the last dot. There must be no directory separator after the dot. + * The extension check is case-sensitive on all platforms. + * + * @param filename the filename to query, null returns false + * @param extensions the extensions to check for, null checks for no extension + * @return true if the filename is one of the extensions + */ + public static boolean isExtension(String filename, Collection extensions) { + if (filename == null) { + return false; + } + if (extensions == null || extensions.isEmpty()) { + return indexOfExtension(filename) == -1; + } + String fileExt = getExtension(filename); + for (String extension : extensions) { + if (fileExt.equals(extension)) { + return true; + } + } + return false; + } + + //----------------------------------------------------------------------- + + /** + * Splits a string into a number of tokens. + * The text is split by '?' and '*'. + * Where multiple '*' occur consecutively they are collapsed into a single '*'. + * + * @param text the text to split + * @return the array of tokens, never null + */ + static String[] splitOnTokens(String text) { + // used by wildcardMatch + // package level so a unit test may run on this + + if (text.indexOf('?') == -1 && text.indexOf('*') == -1) { + return new String[] { text }; + } + + char[] array = text.toCharArray(); + ArrayList list = new ArrayList(); + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + if (array[i] == '?' || array[i] == '*') { + if (buffer.length() != 0) { + list.add(buffer.toString()); + buffer.setLength(0); + } + if (array[i] == '?') { + list.add("?"); + } else if (list.isEmpty() || + i > 0 && list.get(list.size() - 1).equals("*") == false) { + list.add("*"); + } + } else { + buffer.append(array[i]); + } + } + if (buffer.length() != 0) { + list.add(buffer.toString()); + } + + return list.toArray( new String[ list.size() ] ); + } + +} diff --git a/src/com/litesuits/common/io/IOUtils.java b/app/src/main/java/com/litesuits/common/io/IOUtils.java similarity index 97% rename from src/com/litesuits/common/io/IOUtils.java rename to app/src/main/java/com/litesuits/common/io/IOUtils.java index 30355c7..5cd7fd0 100644 --- a/src/com/litesuits/common/io/IOUtils.java +++ b/app/src/main/java/com/litesuits/common/io/IOUtils.java @@ -1,2409 +1,2409 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import android.os.Build; -import com.litesuits.common.io.stream.*; - -import java.io.*; -import java.net.*; -import java.nio.channels.Selector; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * General IO stream manipulation utilities. - *

- * This class provides static utility methods for input/output operations. - *

    - *
  • closeQuietly - these methods close a stream ignoring nulls and exceptions - *
  • toXxx/read - these methods read data from a stream - *
  • write - these methods write data to a stream - *
  • copy - these methods copy all the data from one stream to another - *
  • contentEquals - these methods compare the content of two streams - *
- *

- * The byte-to-char methods and char-to-byte methods involve a conversion step. - * Two methods are provided in each case, one that uses the platform default - * encoding and the other which allows you to specify an encoding. You are - * encouraged to always specify an encoding because relying on the platform - * default can lead to unexpected results, for example when moving from - * development to production. - *

- * All the methods in this class that read a stream are buffered internally. - * This means that there is no cause to use a BufferedInputStream - * or BufferedReader. The default buffer size of 4K has been shown - * to be efficient in tests. - *

- * Wherever possible, the methods in this class do not flush or close - * the stream. This is to avoid making non-portable assumptions about the - * streams' origin and further use. Thus the caller is still responsible for - * closing streams after use. - *

- * Origin of code: Excalibur. - * - * @version $Id: IOUtils.java 1326636 2012-04-16 14:54:53Z ggregory $ - */ -public class IOUtils { - // NOTE: This class is focussed on InputStream, OutputStream, Reader and - // Writer. Each method should take at least one of these as a parameter, - // or return one of them. - - private static final int EOF = -1; - /** - * The Unix directory separator character. - */ - public static final char DIR_SEPARATOR_UNIX = '/'; - /** - * The Windows directory separator character. - */ - public static final char DIR_SEPARATOR_WINDOWS = '\\'; - /** - * The system directory separator character. - */ - public static final char DIR_SEPARATOR = File.separatorChar; - /** - * The Unix line separator string. - */ - public static final String LINE_SEPARATOR_UNIX = "\n"; - /** - * The Windows line separator string. - */ - public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; - /** - * The system line separator string. - */ - public static final String LINE_SEPARATOR; - - static { - // avoid security issues - StringBuilderWriter buf = new StringBuilderWriter(4); - PrintWriter out = new PrintWriter(buf); - out.println(); - LINE_SEPARATOR = buf.toString(); - out.close(); - } - - /** - * The default buffer size ({@value}) to use for - * {@link #copyLarge(java.io.InputStream, java.io.OutputStream)} - * and - * {@link #copyLarge(java.io.Reader, java.io.Writer)} - */ - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - - /** - * The default buffer size to use for the skip() methods. - */ - private static final int SKIP_BUFFER_SIZE = 2048; - - // Allocated in the relevant skip method if necessary. - /* - * N.B. no need to synchronize these because: - * - we don't care if the buffer is created multiple times (the data is ignored) - * - we always use the same size buffer, so if it it is recreated it will still be OK - * (if the buffer size were variable, we would need to synch. to ensure some other thread - * did not create a smaller one) - */ - private static char[] SKIP_CHAR_BUFFER; - private static byte[] SKIP_BYTE_BUFFER; - - /** - * Instances should NOT be constructed in standard programming. - */ - public IOUtils() { - super(); - } - - //----------------------------------------------------------------------- - - /** - * Closes a URLConnection. - * - * @param conn the connection to close. - * @since 2.4 - */ - public static void close(URLConnection conn) { - if (conn instanceof HttpURLConnection) { - ((HttpURLConnection) conn).disconnect(); - } - } - - /** - * Unconditionally close an Reader. - *

- * Equivalent to {@link java.io.Reader#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   char[] data = new char[1024];
-     *   Reader in = null;
-     *   try {
-     *       in = new FileReader("foo.txt");
-     *       in.read(data);
-     *       in.close(); //close errors are handled
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(in);
-     *   }
-     * 
- * - * @param input the Reader to close, may be null or already closed - */ - public static void closeQuietly(Reader input) { - closeQuietly((Closeable) input); - } - - /** - * Unconditionally close a Writer. - *

- * Equivalent to {@link java.io.Writer#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   Writer out = null;
-     *   try {
-     *       out = new StringWriter();
-     *       out.write("Hello World");
-     *       out.close(); //close errors are handled
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(out);
-     *   }
-     * 
- * - * @param output the Writer to close, may be null or already closed - */ - public static void closeQuietly(Writer output) { - closeQuietly((Closeable) output); - } - - /** - * Unconditionally close an InputStream. - *

- * Equivalent to {@link java.io.InputStream#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   byte[] data = new byte[1024];
-     *   InputStream in = null;
-     *   try {
-     *       in = new FileInputStream("foo.txt");
-     *       in.read(data);
-     *       in.close(); //close errors are handled
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(in);
-     *   }
-     * 
- * - * @param input the InputStream to close, may be null or already closed - */ - public static void closeQuietly(InputStream input) { - closeQuietly((Closeable) input); - } - - /** - * Unconditionally close an OutputStream. - *

- * Equivalent to {@link java.io.OutputStream#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     * byte[] data = "Hello, World".getBytes();
-     *
-     * OutputStream out = null;
-     * try {
-     *     out = new FileOutputStream("foo.txt");
-     *     out.write(data);
-     *     out.close(); //close errors are handled
-     * } catch (IOException e) {
-     *     // error handling
-     * } finally {
-     *     IOUtils.closeQuietly(out);
-     * }
-     * 
- * - * @param output the OutputStream to close, may be null or already closed - */ - public static void closeQuietly(OutputStream output) { - closeQuietly((Closeable) output); - } - - /** - * Unconditionally close a Closeable. - *

- * Equivalent to {@link java.io.Closeable#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   Closeable closeable = null;
-     *   try {
-     *       closeable = new FileReader("foo.txt");
-     *       // process closeable
-     *       closeable.close();
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(closeable);
-     *   }
-     * 
- * - * @param closeable the object to close, may be null or already closed - * @since 2.0 - */ - public static void closeQuietly(Closeable closeable) { - try { - if (closeable != null) { - closeable.close(); - } - } catch (IOException ioe) { - // ignore - } - } - - /** - * Unconditionally close a Socket. - *

- * Equivalent to {@link java.net.Socket#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   Socket socket = null;
-     *   try {
-     *       socket = new Socket("http://www.foo.com/", 80);
-     *       // process socket
-     *       socket.close();
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(socket);
-     *   }
-     * 
- * - * @param sock the Socket to close, may be null or already closed - * @since 2.0 - */ - public static void closeQuietly(Socket sock) { - if (sock != null) { - try { - sock.close(); - } catch (IOException ioe) { - // ignored - } - } - } - - /** - * Unconditionally close a Selector. - *

- * Equivalent to {@link java.nio.channels.Selector#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   Selector selector = null;
-     *   try {
-     *       selector = Selector.open();
-     *       // process socket
-     *
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(selector);
-     *   }
-     * 
- * - * @param selector the Selector to close, may be null or already closed - * @since 2.2 - */ - public static void closeQuietly(Selector selector) { - if (selector != null) { - try { - selector.close(); - } catch (IOException ioe) { - // ignored - } - } - } - - /** - * Unconditionally close a ServerSocket. - *

- * Equivalent to {@link java.net.ServerSocket#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

- * Example code: - *

-     *   ServerSocket socket = null;
-     *   try {
-     *       socket = new ServerSocket();
-     *       // process socket
-     *       socket.close();
-     *   } catch (Exception e) {
-     *       // error handling
-     *   } finally {
-     *       IOUtils.closeQuietly(socket);
-     *   }
-     * 
- * - * @param sock the ServerSocket to close, may be null or already closed - * @since 2.2 - */ - public static void closeQuietly(ServerSocket sock) { - if (sock != null) { - try { - sock.close(); - } catch (IOException ioe) { - // ignored - } - } - } - - /** - * Fetches entire contents of an InputStream and represent - * same data as result InputStream. - *

- * This method is useful where, - *

    - *
  • Source InputStream is slow.
  • - *
  • It has network resources associated, so we cannot keep it open for - * long time.
  • - *
  • It has network timeout associated.
  • - *
- * It can be used in favor of {@link #toByteArray(java.io.InputStream)}, since it - * avoids unnecessary allocation and copy of byte[].
- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input Stream to be fully buffered. - * @return A fully buffered stream. - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static InputStream toBufferedInputStream(InputStream input) throws IOException { - return com.litesuits.common.io.stream.ByteArrayOutputStream.toBufferedInputStream(input); - } - - /** - * Returns the given reader if it is a {@link java.io.BufferedReader}, otherwise creates a toBufferedReader for the given - * reader. - * - * @param reader the reader to wrap or return - * @return the given reader or a new {@link java.io.BufferedReader} for the given reader - * @since 2.2 - */ - public static BufferedReader toBufferedReader(Reader reader) { - return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); - } - - // read toByteArray - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a byte[]. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static byte[] toByteArray(InputStream input) throws IOException { - com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); - copy(input, output); - return output.toByteArray(); - } - - /** - * Get contents of an InputStream as a byte[]. - * Use this method instead of toByteArray(InputStream) - * when InputStream size is known. - * NOTE: the method checks that the length can safely be cast to an int without truncation - * before using {@link com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int)} to read into the byte array. - * (Arrays can have no more than Integer.MAX_VALUE entries anyway) - * - * @param input the InputStream to read from - * @param size the size of InputStream - * @return the requested byte array - * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size - * @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE - * @see com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int) - * @since 2.1 - */ - public static byte[] toByteArray(InputStream input, long size) throws IOException { - - if (size > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); - } - - return toByteArray(input, (int) size); - } - - /** - * Get the contents of an InputStream as a byte[]. - * Use this method instead of toByteArray(InputStream) - * when InputStream size is known - * - * @param input the InputStream to read from - * @param size the size of InputStream - * @return the requested byte array - * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size - * @throws IllegalArgumentException if size is less than zero - * @since 2.1 - */ - public static byte[] toByteArray(InputStream input, int size) throws IOException { - - if (size < 0) { - throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); - } - - if (size == 0) { - return new byte[0]; - } - - byte[] data = new byte[size]; - int offset = 0; - int readed; - - while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) { - offset += readed; - } - - if (offset != size) { - throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size); - } - - return data; - } - - /** - * Get the contents of a Reader as a byte[] - * using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static byte[] toByteArray(Reader input) throws IOException { - return toByteArray(input, Charset.defaultCharset()); - } - - /** - * Get the contents of a Reader as a byte[] - * using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @param encoding the encoding to use, null means platform default - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static byte[] toByteArray(Reader input, Charset encoding) throws IOException { - com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); - copy(input, output, encoding); - return output.toByteArray(); - } - - /** - * Get the contents of a Reader as a byte[] - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @param encoding the encoding to use, null means platform default - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static byte[] toByteArray(Reader input, String encoding) throws IOException { - return toByteArray(input, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a String as a byte[] - * using the default character encoding of the platform. - *

- * This is the same as {@link String#getBytes()}. - * - * @param input the String to convert - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs (never occurs) - * @deprecated Use {@link String#getBytes()} - */ - @Deprecated - public static byte[] toByteArray(String input) throws IOException { - return input.getBytes(); - } - - /** - * Get the contents of a URI as a byte[]. - * - * @param uri the URI to read - * @return the requested byte array - * @throws NullPointerException if the uri is null - * @throws java.io.IOException if an I/O exception occurs - * @since 2.4 - */ - public static byte[] toByteArray(URI uri) throws IOException { - return IOUtils.toByteArray(uri.toURL()); - } - - /** - * Get the contents of a URL as a byte[]. - * - * @param url the URL to read - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O exception occurs - * @since 2.4 - */ - public static byte[] toByteArray(URL url) throws IOException { - URLConnection conn = url.openConnection(); - try { - return IOUtils.toByteArray(conn); - } finally { - close(conn); - } - } - - /** - * Get the contents of a URLConnection as a byte[]. - * - * @param urlConn the URLConnection to read - * @return the requested byte array - * @throws NullPointerException if the urlConn is null - * @throws java.io.IOException if an I/O exception occurs - * @since 2.4 - */ - public static byte[] toByteArray(URLConnection urlConn) throws IOException { - InputStream inputStream = urlConn.getInputStream(); - try { - return IOUtils.toByteArray(inputStream); - } finally { - inputStream.close(); - } - } - - // read char[] - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a character array - * using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static char[] toCharArray(InputStream is) throws IOException { - return toCharArray(is, Charset.defaultCharset()); - } - - /** - * Get the contents of an InputStream as a character array - * using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static char[] toCharArray(InputStream is, Charset encoding) - throws IOException { - CharArrayWriter output = new CharArrayWriter(); - copy(is, output, encoding); - return output.toCharArray(); - } - - /** - * Get the contents of an InputStream as a character array - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static char[] toCharArray(InputStream is, String encoding) throws IOException { - return toCharArray(is, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a Reader as a character array. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static char[] toCharArray(Reader input) throws IOException { - CharArrayWriter sw = new CharArrayWriter(); - copy(input, sw); - return sw.toCharArray(); - } - - // read toString - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a String - * using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static String toString(InputStream input) throws IOException { - return toString(input, Charset.defaultCharset()); - } - - /** - * Get the contents of an InputStream as a String - * using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * - * @param input the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static String toString(InputStream input, Charset encoding) throws IOException { - StringBuilderWriter sw = new StringBuilderWriter(); - copy(input, sw, encoding); - return sw.toString(); - } - - /** - * Get the contents of an InputStream as a String - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - */ - public static String toString(InputStream input, String encoding) - throws IOException { - return toString(input, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a Reader as a String. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static String toString(Reader input) throws IOException { - StringBuilderWriter sw = new StringBuilderWriter(); - copy(input, sw); - return sw.toString(); - } - - /** - * Gets the contents at the given URI. - * - * @param uri The URI source. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.1 - */ - public static String toString(URI uri) throws IOException { - return toString(uri, Charset.defaultCharset()); - } - - /** - * Gets the contents at the given URI. - * - * @param uri The URI source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.3. - */ - public static String toString(URI uri, Charset encoding) throws IOException { - return toString(uri.toURL(), Charsets.toCharset(encoding)); - } - - /** - * Gets the contents at the given URI. - * - * @param uri The URI source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.1 - */ - public static String toString(URI uri, String encoding) throws IOException { - return toString(uri, Charsets.toCharset(encoding)); - } - - /** - * Gets the contents at the given URL. - * - * @param url The URL source. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.1 - */ - public static String toString(URL url) throws IOException { - return toString(url, Charset.defaultCharset()); - } - - /** - * Gets the contents at the given URL. - * - * @param url The URL source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.3 - */ - public static String toString(URL url, Charset encoding) throws IOException { - InputStream inputStream = url.openStream(); - try { - return toString(inputStream, encoding); - } finally { - inputStream.close(); - } - } - - /** - * Gets the contents at the given URL. - * - * @param url The URL source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.1 - */ - public static String toString(URL url, String encoding) throws IOException { - return toString(url, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a byte[] as a String - * using the default character encoding of the platform. - * - * @param input the byte array to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs (never occurs) - * @deprecated Use {@link String#String(byte[])} - */ - @Deprecated - public static String toString(byte[] input) throws IOException { - return new String(input); - } - - /** - * Get the contents of a byte[] as a String - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - * - * @param input the byte array to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs (never occurs) - */ - public static String toString(byte[] input, String encoding) throws IOException { - return new String(input, encoding); - } - - // readLines - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a list of Strings, - * one entry per line, using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static List readLines(InputStream input) throws IOException { - return readLines(input, Charset.defaultCharset()); - } - - /** - * Get the contents of an InputStream as a list of Strings, - * one entry per line, using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @param encoding the encoding to use, null means platform default - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static List readLines(InputStream input, Charset encoding) throws IOException { - InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(encoding)); - return readLines(reader); - } - - /** - * Get the contents of an InputStream as a list of Strings, - * one entry per line, using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @param encoding the encoding to use, null means platform default - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static List readLines(InputStream input, String encoding) throws IOException { - return readLines(input, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a Reader as a list of Strings, - * one entry per line. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from, not null - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static List readLines(Reader input) throws IOException { - BufferedReader reader = toBufferedReader(input); - List list = new ArrayList(); - String line = reader.readLine(); - while (line != null) { - list.add(line); - line = reader.readLine(); - } - return list; - } - - //----------------------------------------------------------------------- - - /** - * Convert the specified CharSequence to an input stream, encoded as bytes - * using the default character encoding of the platform. - * - * @param input the CharSequence to convert - * @return an input stream - * @since 2.0 - */ - public static InputStream toInputStream(CharSequence input) { - return toInputStream(input, Charset.defaultCharset()); - } - - /** - * Convert the specified CharSequence to an input stream, encoded as bytes - * using the specified character encoding. - * - * @param input the CharSequence to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @since 2.3 - */ - public static InputStream toInputStream(CharSequence input, Charset encoding) { - return toInputStream(input.toString(), encoding); - } - - /** - * Convert the specified CharSequence to an input stream, encoded as bytes - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - * - * @param input the CharSequence to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @throws java.io.IOException if the encoding is invalid - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.0 - */ - public static InputStream toInputStream(CharSequence input, String encoding) throws IOException { - return toInputStream(input, Charsets.toCharset(encoding)); - } - - //----------------------------------------------------------------------- - - /** - * Convert the specified string to an input stream, encoded as bytes - * using the default character encoding of the platform. - * - * @param input the string to convert - * @return an input stream - * @since 1.1 - */ - public static InputStream toInputStream(String input) { - return toInputStream(input, Charset.defaultCharset()); - } - - /** - * Convert the specified string to an input stream, encoded as bytes - * using the specified character encoding. - * - * @param input the string to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @since 2.3 - */ - public static InputStream toInputStream(String input, Charset encoding) { - return new ByteArrayInputStream(StringCodingUtils.getBytes(input, Charsets.toCharset(encoding))); - } - - /** - * Convert the specified string to an input stream, encoded as bytes - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - * - * @param input the string to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @throws java.io.IOException if the encoding is invalid - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static InputStream toInputStream(String input, String encoding) throws IOException { - byte[] bytes = StringCodingUtils.getBytes(input,Charsets.toCharset(encoding)); - return new ByteArrayInputStream(bytes); - } - - // write byte[] - //----------------------------------------------------------------------- - - /** - * Writes bytes from a byte[] to an OutputStream. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(byte[] data, OutputStream output) - throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the default character encoding of the platform. - *

- * This method uses {@link String#String(byte[])}. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(byte[] data, Writer output) throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the specified character encoding. - *

- * This method uses {@link String#String(byte[], String)}. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(byte[] data, Writer output, Charset encoding) throws IOException { - if (data != null) { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD){ - output.write(new String(data, Charsets.toCharset(encoding).name())); - }else{ - output.write(new String(data, Charsets.toCharset(encoding))); - } - } - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#String(byte[], String)}. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void write(byte[] data, Writer output, String encoding) throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write char[] - //----------------------------------------------------------------------- - - /** - * Writes chars from a char[] to a Writer - * using the default character encoding of the platform. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(char[] data, Writer output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream. - *

- * This method uses {@link String#String(char[])} and - * {@link String#getBytes()}. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(char[] data, OutputStream output) - throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream using the specified character encoding. - *

- * This method uses {@link String#String(char[])} and - * {@link String#getBytes(String)}. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(char[] data, OutputStream output, Charset encoding) throws IOException { - if (data != null) { - output.write(StringCodingUtils.getBytes(new String(data), Charsets.toCharset(encoding))); - } - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#String(char[])} and - * {@link String#getBytes(String)}. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void write(char[] data, OutputStream output, String encoding) - throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write CharSequence - //----------------------------------------------------------------------- - - /** - * Writes chars from a CharSequence to a Writer. - * - * @param data the CharSequence to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static void write(CharSequence data, Writer output) throws IOException { - if (data != null) { - write(data.toString(), output); - } - } - - /** - * Writes chars from a CharSequence to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

- * This method uses {@link String#getBytes()}. - * - * @param data the CharSequence to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static void write(CharSequence data, OutputStream output) - throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes chars from a CharSequence to bytes on an - * OutputStream using the specified character encoding. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the CharSequence to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(CharSequence data, OutputStream output, Charset encoding) throws IOException { - if (data != null) { - write(data.toString(), output, encoding); - } - } - - /** - * Writes chars from a CharSequence to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the CharSequence to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.0 - */ - public static void write(CharSequence data, OutputStream output, String encoding) throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write String - //----------------------------------------------------------------------- - - /** - * Writes chars from a String to a Writer. - * - * @param data the String to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(String data, Writer output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

- * This method uses {@link String#getBytes()}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(String data, OutputStream output) - throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the specified character encoding. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(String data, OutputStream output, Charset encoding) throws IOException { - if (data != null) { - output.write(StringCodingUtils.getBytes(data, Charsets.toCharset(encoding))); - } - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void write(String data, OutputStream output, String encoding) - throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write StringBuffer - //----------------------------------------------------------------------- - - /** - * Writes chars from a StringBuffer to a Writer. - * - * @param data the StringBuffer to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - * @deprecated replaced by write(CharSequence, Writer) - */ - @Deprecated - public static void write(StringBuffer data, Writer output) - throws IOException { - if (data != null) { - output.write(data.toString()); - } - } - - /** - * Writes chars from a StringBuffer to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

- * This method uses {@link String#getBytes()}. - * - * @param data the StringBuffer to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - * @deprecated replaced by write(CharSequence, OutputStream) - */ - @Deprecated - public static void write(StringBuffer data, OutputStream output) - throws IOException { - write(data, output, (String) null); - } - - /** - * Writes chars from a StringBuffer to bytes on an - * OutputStream using the specified character encoding. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link String#getBytes(String)}. - * - * @param data the StringBuffer to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - * @deprecated replaced by write(CharSequence, OutputStream, String) - */ - @Deprecated - public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException { - if (data != null) { - output.write(StringCodingUtils.getBytes(data.toString(), Charsets.toCharset(encoding))); - } - } - - // writeLines - //----------------------------------------------------------------------- - - /** - * Writes the toString() value of each item in a collection to - * an OutputStream line by line, using the default character - * encoding of the platform and the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @throws NullPointerException if the output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, - OutputStream output) throws IOException { - writeLines(lines, lineEnding, output, Charset.defaultCharset()); - } - - /** - * Writes the toString() value of each item in a collection to - * an OutputStream line by line, using the specified character - * encoding and the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void writeLines(Collection lines, String lineEnding, OutputStream output, Charset encoding) - throws IOException { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - Charset cs = Charsets.toCharset(encoding); - for (Object line : lines) { - if (line != null) { - output.write(StringCodingUtils.getBytes(line.toString(), cs)); - } - output.write(StringCodingUtils.getBytes(lineEnding,cs)); - } - } - - /** - * Writes the toString() value of each item in a collection to - * an OutputStream line by line, using the specified character - * encoding and the specified line ending. - *

- * Character encoding names can be found at - * IANA. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, - OutputStream output, String encoding) throws IOException { - writeLines(lines, lineEnding, output, Charsets.toCharset(encoding)); - } - - /** - * Writes the toString() value of each item in a collection to - * a Writer line by line, using the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param writer the Writer to write to, not null, not closed - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, - Writer writer) throws IOException { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - for (Object line : lines) { - if (line != null) { - writer.write(line.toString()); - } - writer.write(lineEnding); - } - } - - // copy from InputStream - //----------------------------------------------------------------------- - - /** - * Copy bytes from an InputStream to an - * OutputStream. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * Large streams (over 2GB) will return a bytes copied value of - * -1 after the copy has completed since the correct - * number of bytes cannot be returned as an int. For large streams - * use the copyLarge(InputStream, OutputStream) method. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static int copy(InputStream input, OutputStream output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copy bytes from a large (over 2GB) InputStream to an - * OutputStream. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.3 - */ - public static long copyLarge(InputStream input, OutputStream output) - throws IOException { - return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy bytes from a large (over 2GB) InputStream to an - * OutputStream. - *

- * This method uses the provided buffer, so there is no need to use a - * BufferedInputStream. - *

- * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @param buffer the buffer to use for the copy - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) - throws IOException { - long count = 0; - int n = 0; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Copy some or all bytes from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input bytes. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @param inputOffset : number of bytes to skip from input before copying - * -ve values are ignored - * @param length : number of bytes to copy. -ve means all - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(InputStream input, OutputStream output, long inputOffset, long length) - throws IOException { - return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy some or all bytes from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input bytes. - *

- * This method uses the provided buffer, so there is no need to use a - * BufferedInputStream. - *

- * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @param inputOffset : number of bytes to skip from input before copying - * -ve values are ignored - * @param length : number of bytes to copy. -ve means all - * @param buffer the buffer to use for the copy - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(InputStream input, OutputStream output, - final long inputOffset, final long length, byte[] buffer) throws IOException { - if (inputOffset > 0) { - skipFully(input, inputOffset); - } - if (length == 0) { - return 0; - } - final int bufferLength = buffer.length; - int bytesToRead = bufferLength; - if (length > 0 && length < bufferLength) { - bytesToRead = (int) length; - } - int read; - long totalRead = 0; - while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { - output.write(buffer, 0, read); - totalRead += read; - if (length > 0) { // only adjust length if not reading to the end - // Note the cast must work because buffer.length is an integer - bytesToRead = (int) Math.min(length - totalRead, bufferLength); - } - } - return totalRead; - } - - /** - * Copy bytes from an InputStream to chars on a - * Writer using the default character encoding of the platform. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * This method uses {@link java.io.InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void copy(InputStream input, Writer output) - throws IOException { - copy(input, output, Charset.defaultCharset()); - } - - /** - * Copy bytes from an InputStream to chars on a - * Writer using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * This method uses {@link java.io.InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void copy(InputStream input, Writer output, Charset encoding) throws IOException { - InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(encoding)); - copy(in, output); - } - - /** - * Copy bytes from an InputStream to chars on a - * Writer using the specified character encoding. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

- * Character encoding names can be found at - * IANA. - *

- * This method uses {@link java.io.InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void copy(InputStream input, Writer output, String encoding) throws IOException { - copy(input, output, Charsets.toCharset(encoding)); - } - - // copy from Reader - //----------------------------------------------------------------------- - - /** - * Copy chars from a Reader to a Writer. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Large streams (over 2GB) will return a chars copied value of - * -1 after the copy has completed since the correct - * number of chars cannot be returned as an int. For large streams - * use the copyLarge(Reader, Writer) method. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied, or -1 if > Integer.MAX_VALUE - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static int copy(Reader input, Writer output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copy chars from a large (over 2GB) Reader to a Writer. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.3 - */ - public static long copyLarge(Reader input, Writer output) throws IOException { - return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy chars from a large (over 2GB) Reader to a Writer. - *

- * This method uses the provided buffer, so there is no need to use a - * BufferedReader. - *

- * - * @param input the Reader to read from - * @param output the Writer to write to - * @param buffer the buffer to be used for the copy - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException { - long count = 0; - int n = 0; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Copy some or all chars from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input chars. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @param inputOffset : number of chars to skip from input before copying - * -ve values are ignored - * @param length : number of chars to copy. -ve means all - * @return the number of chars copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length) - throws IOException { - return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy some or all chars from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input chars. - *

- * This method uses the provided buffer, so there is no need to use a - * BufferedReader. - *

- * - * @param input the Reader to read from - * @param output the Writer to write to - * @param inputOffset : number of chars to skip from input before copying - * -ve values are ignored - * @param length : number of chars to copy. -ve means all - * @param buffer the buffer to be used for the copy - * @return the number of chars copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length, char[] buffer) - throws IOException { - if (inputOffset > 0) { - skipFully(input, inputOffset); - } - if (length == 0) { - return 0; - } - int bytesToRead = buffer.length; - if (length > 0 && length < buffer.length) { - bytesToRead = (int) length; - } - int read; - long totalRead = 0; - while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { - output.write(buffer, 0, read); - totalRead += read; - if (length > 0) { // only adjust length if not reading to the end - // Note the cast must work because buffer.length is an integer - bytesToRead = (int) Math.min(length - totalRead, buffer.length); - } - } - return totalRead; - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the default character encoding of the - * platform, and calling flush. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

- * This method uses {@link java.io.OutputStreamWriter}. - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void copy(Reader input, OutputStream output) - throws IOException { - copy(input, output, Charset.defaultCharset()); - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the specified character encoding, and - * calling flush. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- *

- * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

- *

- * This method uses {@link java.io.OutputStreamWriter}. - *

- * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException { - OutputStreamWriter out = new OutputStreamWriter(output, Charsets.toCharset(encoding)); - copy(input, out); - // XXX Unless anyone is planning on rewriting OutputStreamWriter, - // we have to flush here. - out.flush(); - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the specified character encoding, and - * calling flush. - *

- * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

- * Character encoding names can be found at - * IANA. - *

- * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

- * This method uses {@link java.io.OutputStreamWriter}. - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void copy(Reader input, OutputStream output, String encoding) throws IOException { - copy(input, output, Charsets.toCharset(encoding)); - } - - // content equals - //----------------------------------------------------------------------- - - /** - * Compare the contents of two Streams to determine if they are equal or - * not. - *

- * This method buffers the input internally using - * BufferedInputStream if they are not already buffered. - * - * @param input1 the first stream - * @param input2 the second stream - * @return true if the content of the streams are equal or they both don't - * exist, false otherwise - * @throws NullPointerException if either input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static boolean contentEquals(InputStream input1, InputStream input2) - throws IOException { - if (!(input1 instanceof BufferedInputStream)) { - input1 = new BufferedInputStream(input1); - } - if (!(input2 instanceof BufferedInputStream)) { - input2 = new BufferedInputStream(input2); - } - - int ch = input1.read(); - while (EOF != ch) { - int ch2 = input2.read(); - if (ch != ch2) { - return false; - } - ch = input1.read(); - } - - int ch2 = input2.read(); - return ch2 == EOF; - } - - /** - * Compare the contents of two Readers to determine if they are equal or - * not. - *

- * This method buffers the input internally using - * BufferedReader if they are not already buffered. - * - * @param input1 the first reader - * @param input2 the second reader - * @return true if the content of the readers are equal or they both don't - * exist, false otherwise - * @throws NullPointerException if either input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static boolean contentEquals(Reader input1, Reader input2) - throws IOException { - - input1 = toBufferedReader(input1); - input2 = toBufferedReader(input2); - - int ch = input1.read(); - while (EOF != ch) { - int ch2 = input2.read(); - if (ch != ch2) { - return false; - } - ch = input1.read(); - } - - int ch2 = input2.read(); - return ch2 == EOF; - } - - /** - * Compare the contents of two Readers to determine if they are equal or - * not, ignoring EOL characters. - *

- * This method buffers the input internally using - * BufferedReader if they are not already buffered. - * - * @param input1 the first reader - * @param input2 the second reader - * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise - * @throws NullPointerException if either input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) - throws IOException { - BufferedReader br1 = toBufferedReader(input1); - BufferedReader br2 = toBufferedReader(input2); - - String line1 = br1.readLine(); - String line2 = br2.readLine(); - while (line1 != null && line2 != null && line1.equals(line2)) { - line1 = br1.readLine(); - line2 = br2.readLine(); - } - return line1 == null ? line2 == null ? true : false : line1.equals(line2); - } - - /** - * Skip bytes from an input byte stream. - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input byte stream to skip - * @param toSkip number of bytes to skip. - * @return number of bytes actually skipped. - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @see java.io.InputStream#skip(long) - * @since 2.0 - */ - public static long skip(InputStream input, long toSkip) throws IOException { - if (toSkip < 0) { - throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); - } - /* - * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data - * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer - * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) - */ - if (SKIP_BYTE_BUFFER == null) { - SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE]; - } - long remain = toSkip; - while (remain > 0) { - long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); - if (n < 0) { // EOF - break; - } - remain -= n; - } - return toSkip - remain; - } - - /** - * Skip characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input character stream to skip - * @param toSkip number of characters to skip. - * @return number of characters actually skipped. - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @see java.io.Reader#skip(long) - * @since 2.0 - */ - public static long skip(Reader input, long toSkip) throws IOException { - if (toSkip < 0) { - throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); - } - /* - * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data - * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer - * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) - */ - if (SKIP_CHAR_BUFFER == null) { - SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE]; - } - long remain = toSkip; - while (remain > 0) { - long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); - if (n < 0) { // EOF - break; - } - remain -= n; - } - return toSkip - remain; - } - - /** - * Skip the requested number of bytes or fail if there are not enough left. - *

- * This allows for the possibility that {@link java.io.InputStream#skip(long)} may - * not skip as many bytes as requested (most likely because of reaching EOF). - * - * @param input stream to skip - * @param toSkip the number of bytes to skip - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @throws java.io.EOFException if the number of bytes skipped was incorrect - * @see java.io.InputStream#skip(long) - * @since 2.0 - */ - public static void skipFully(InputStream input, long toSkip) throws IOException { - if (toSkip < 0) { - throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); - } - long skipped = skip(input, toSkip); - if (skipped != toSkip) { - throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); - } - } - - /** - * Skip the requested number of characters or fail if there are not enough left. - *

- * This allows for the possibility that {@link java.io.Reader#skip(long)} may - * not skip as many characters as requested (most likely because of reaching EOF). - * - * @param input stream to skip - * @param toSkip the number of characters to skip - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @throws java.io.EOFException if the number of characters skipped was incorrect - * @see java.io.Reader#skip(long) - * @since 2.0 - */ - public static void skipFully(Reader input, long toSkip) throws IOException { - long skipped = skip(input, toSkip); - if (skipped != toSkip) { - throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped); - } - } - - - /** - * Read characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(Reader input, char[] buffer, int offset, int length) throws IOException { - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative: " + length); - } - int remaining = length; - while (remaining > 0) { - int location = length - remaining; - int count = input.read(buffer, offset + location, remaining); - if (EOF == count) { // EOF - break; - } - remaining -= count; - } - return length - remaining; - } - - /** - * Read characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input where to read input from - * @param buffer destination - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(Reader input, char[] buffer) throws IOException { - return read(input, buffer, 0, buffer.length); - } - - /** - * Read bytes from an input stream. - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.InputStream}. - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException { - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative: " + length); - } - int remaining = length; - while (remaining > 0) { - int location = length - remaining; - int count = input.read(buffer, offset + location, remaining); - if (EOF == count) { // EOF - break; - } - remaining -= count; - } - return length - remaining; - } - - /** - * Read bytes from an input stream. - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.InputStream}. - * - * @param input where to read input from - * @param buffer destination - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(InputStream input, byte[] buffer) throws IOException { - return read(input, buffer, 0, buffer.length); - } - - /** - * Read the requested number of characters or fail if there are not enough left. - *

- * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may - * not read as many characters as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of characters read was incorrect - * @since 2.2 - */ - public static void readFully(Reader input, char[] buffer, int offset, int length) throws IOException { - int actual = read(input, buffer, offset, length); - if (actual != length) { - throw new EOFException("Length to read: " + length + " actual: " + actual); - } - } - - /** - * Read the requested number of characters or fail if there are not enough left. - *

- * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may - * not read as many characters as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of characters read was incorrect - * @since 2.2 - */ - public static void readFully(Reader input, char[] buffer) throws IOException { - readFully(input, buffer, 0, buffer.length); - } - - /** - * Read the requested number of bytes or fail if there are not enough left. - *

- * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of bytes read was incorrect - * @since 2.2 - */ - public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException { - int actual = read(input, buffer, offset, length); - if (actual != length) { - throw new EOFException("Length to read: " + length + " actual: " + actual); - } - } - - /** - * Read the requested number of bytes or fail if there are not enough left. - *

- * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of bytes read was incorrect - * @since 2.2 - */ - public static void readFully(InputStream input, byte[] buffer) throws IOException { - readFully(input, buffer, 0, buffer.length); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import android.os.Build; +import com.litesuits.common.io.stream.*; + +import java.io.*; +import java.net.*; +import java.nio.channels.Selector; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * General IO stream manipulation utilities. + *

+ * This class provides static utility methods for input/output operations. + *

    + *
  • closeQuietly - these methods close a stream ignoring nulls and exceptions + *
  • toXxx/read - these methods read data from a stream + *
  • write - these methods write data to a stream + *
  • copy - these methods copy all the data from one stream to another + *
  • contentEquals - these methods compare the content of two streams + *
+ *

+ * The byte-to-char methods and char-to-byte methods involve a conversion step. + * Two methods are provided in each case, one that uses the platform default + * encoding and the other which allows you to specify an encoding. You are + * encouraged to always specify an encoding because relying on the platform + * default can lead to unexpected results, for example when moving from + * development to production. + *

+ * All the methods in this class that read a stream are buffered internally. + * This means that there is no cause to use a BufferedInputStream + * or BufferedReader. The default buffer size of 4K has been shown + * to be efficient in tests. + *

+ * Wherever possible, the methods in this class do not flush or close + * the stream. This is to avoid making non-portable assumptions about the + * streams' origin and further use. Thus the caller is still responsible for + * closing streams after use. + *

+ * Origin of code: Excalibur. + * + * @version $Id: IOUtils.java 1326636 2012-04-16 14:54:53Z ggregory $ + */ +public class IOUtils { + // NOTE: This class is focussed on InputStream, OutputStream, Reader and + // Writer. Each method should take at least one of these as a parameter, + // or return one of them. + + private static final int EOF = -1; + /** + * The Unix directory separator character. + */ + public static final char DIR_SEPARATOR_UNIX = '/'; + /** + * The Windows directory separator character. + */ + public static final char DIR_SEPARATOR_WINDOWS = '\\'; + /** + * The system directory separator character. + */ + public static final char DIR_SEPARATOR = File.separatorChar; + /** + * The Unix line separator string. + */ + public static final String LINE_SEPARATOR_UNIX = "\n"; + /** + * The Windows line separator string. + */ + public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; + /** + * The system line separator string. + */ + public static final String LINE_SEPARATOR; + + static { + // avoid security issues + StringBuilderWriter buf = new StringBuilderWriter(4); + PrintWriter out = new PrintWriter(buf); + out.println(); + LINE_SEPARATOR = buf.toString(); + out.close(); + } + + /** + * The default buffer size ({@value}) to use for + * {@link #copyLarge(java.io.InputStream, java.io.OutputStream)} + * and + * {@link #copyLarge(java.io.Reader, java.io.Writer)} + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * The default buffer size to use for the skip() methods. + */ + private static final int SKIP_BUFFER_SIZE = 2048; + + // Allocated in the relevant skip method if necessary. + /* + * N.B. no need to synchronize these because: + * - we don't care if the buffer is created multiple times (the data is ignored) + * - we always use the same size buffer, so if it it is recreated it will still be OK + * (if the buffer size were variable, we would need to synch. to ensure some other thread + * did not create a smaller one) + */ + private static char[] SKIP_CHAR_BUFFER; + private static byte[] SKIP_BYTE_BUFFER; + + /** + * Instances should NOT be constructed in standard programming. + */ + public IOUtils() { + super(); + } + + //----------------------------------------------------------------------- + + /** + * Closes a URLConnection. + * + * @param conn the connection to close. + * @since 2.4 + */ + public static void close(URLConnection conn) { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } + + /** + * Unconditionally close an Reader. + *

+ * Equivalent to {@link java.io.Reader#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   char[] data = new char[1024];
+     *   Reader in = null;
+     *   try {
+     *       in = new FileReader("foo.txt");
+     *       in.read(data);
+     *       in.close(); //close errors are handled
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(in);
+     *   }
+     * 
+ * + * @param input the Reader to close, may be null or already closed + */ + public static void closeQuietly(Reader input) { + closeQuietly((Closeable) input); + } + + /** + * Unconditionally close a Writer. + *

+ * Equivalent to {@link java.io.Writer#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Writer out = null;
+     *   try {
+     *       out = new StringWriter();
+     *       out.write("Hello World");
+     *       out.close(); //close errors are handled
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(out);
+     *   }
+     * 
+ * + * @param output the Writer to close, may be null or already closed + */ + public static void closeQuietly(Writer output) { + closeQuietly((Closeable) output); + } + + /** + * Unconditionally close an InputStream. + *

+ * Equivalent to {@link java.io.InputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   byte[] data = new byte[1024];
+     *   InputStream in = null;
+     *   try {
+     *       in = new FileInputStream("foo.txt");
+     *       in.read(data);
+     *       in.close(); //close errors are handled
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(in);
+     *   }
+     * 
+ * + * @param input the InputStream to close, may be null or already closed + */ + public static void closeQuietly(InputStream input) { + closeQuietly((Closeable) input); + } + + /** + * Unconditionally close an OutputStream. + *

+ * Equivalent to {@link java.io.OutputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     * byte[] data = "Hello, World".getBytes();
+     *
+     * OutputStream out = null;
+     * try {
+     *     out = new FileOutputStream("foo.txt");
+     *     out.write(data);
+     *     out.close(); //close errors are handled
+     * } catch (IOException e) {
+     *     // error handling
+     * } finally {
+     *     IOUtils.closeQuietly(out);
+     * }
+     * 
+ * + * @param output the OutputStream to close, may be null or already closed + */ + public static void closeQuietly(OutputStream output) { + closeQuietly((Closeable) output); + } + + /** + * Unconditionally close a Closeable. + *

+ * Equivalent to {@link java.io.Closeable#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Closeable closeable = null;
+     *   try {
+     *       closeable = new FileReader("foo.txt");
+     *       // process closeable
+     *       closeable.close();
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(closeable);
+     *   }
+     * 
+ * + * @param closeable the object to close, may be null or already closed + * @since 2.0 + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close a Socket. + *

+ * Equivalent to {@link java.net.Socket#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Socket socket = null;
+     *   try {
+     *       socket = new Socket("http://www.foo.com/", 80);
+     *       // process socket
+     *       socket.close();
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(socket);
+     *   }
+     * 
+ * + * @param sock the Socket to close, may be null or already closed + * @since 2.0 + */ + public static void closeQuietly(Socket sock) { + if (sock != null) { + try { + sock.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Unconditionally close a Selector. + *

+ * Equivalent to {@link java.nio.channels.Selector#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   Selector selector = null;
+     *   try {
+     *       selector = Selector.open();
+     *       // process socket
+     *
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(selector);
+     *   }
+     * 
+ * + * @param selector the Selector to close, may be null or already closed + * @since 2.2 + */ + public static void closeQuietly(Selector selector) { + if (selector != null) { + try { + selector.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Unconditionally close a ServerSocket. + *

+ * Equivalent to {@link java.net.ServerSocket#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

+ * Example code: + *

+     *   ServerSocket socket = null;
+     *   try {
+     *       socket = new ServerSocket();
+     *       // process socket
+     *       socket.close();
+     *   } catch (Exception e) {
+     *       // error handling
+     *   } finally {
+     *       IOUtils.closeQuietly(socket);
+     *   }
+     * 
+ * + * @param sock the ServerSocket to close, may be null or already closed + * @since 2.2 + */ + public static void closeQuietly(ServerSocket sock) { + if (sock != null) { + try { + sock.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Fetches entire contents of an InputStream and represent + * same data as result InputStream. + *

+ * This method is useful where, + *

    + *
  • Source InputStream is slow.
  • + *
  • It has network resources associated, so we cannot keep it open for + * long time.
  • + *
  • It has network timeout associated.
  • + *
+ * It can be used in favor of {@link #toByteArray(java.io.InputStream)}, since it + * avoids unnecessary allocation and copy of byte[].
+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input Stream to be fully buffered. + * @return A fully buffered stream. + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static InputStream toBufferedInputStream(InputStream input) throws IOException { + return com.litesuits.common.io.stream.ByteArrayOutputStream.toBufferedInputStream(input); + } + + /** + * Returns the given reader if it is a {@link java.io.BufferedReader}, otherwise creates a toBufferedReader for the given + * reader. + * + * @param reader the reader to wrap or return + * @return the given reader or a new {@link java.io.BufferedReader} for the given reader + * @since 2.2 + */ + public static BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + } + + // read toByteArray + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a byte[]. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream input) throws IOException { + com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + /** + * Get contents of an InputStream as a byte[]. + * Use this method instead of toByteArray(InputStream) + * when InputStream size is known. + * NOTE: the method checks that the length can safely be cast to an int without truncation + * before using {@link com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int)} to read into the byte array. + * (Arrays can have no more than Integer.MAX_VALUE entries anyway) + * + * @param input the InputStream to read from + * @param size the size of InputStream + * @return the requested byte array + * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size + * @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE + * @see com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int) + * @since 2.1 + */ + public static byte[] toByteArray(InputStream input, long size) throws IOException { + + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); + } + + return toByteArray(input, (int) size); + } + + /** + * Get the contents of an InputStream as a byte[]. + * Use this method instead of toByteArray(InputStream) + * when InputStream size is known + * + * @param input the InputStream to read from + * @param size the size of InputStream + * @return the requested byte array + * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size + * @throws IllegalArgumentException if size is less than zero + * @since 2.1 + */ + public static byte[] toByteArray(InputStream input, int size) throws IOException { + + if (size < 0) { + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + } + + if (size == 0) { + return new byte[0]; + } + + byte[] data = new byte[size]; + int offset = 0; + int readed; + + while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) { + offset += readed; + } + + if (offset != size) { + throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size); + } + + return data; + } + + /** + * Get the contents of a Reader as a byte[] + * using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static byte[] toByteArray(Reader input) throws IOException { + return toByteArray(input, Charset.defaultCharset()); + } + + /** + * Get the contents of a Reader as a byte[] + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static byte[] toByteArray(Reader input, Charset encoding) throws IOException { + com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); + copy(input, output, encoding); + return output.toByteArray(); + } + + /** + * Get the contents of a Reader as a byte[] + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static byte[] toByteArray(Reader input, String encoding) throws IOException { + return toByteArray(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a String as a byte[] + * using the default character encoding of the platform. + *

+ * This is the same as {@link String#getBytes()}. + * + * @param input the String to convert + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#getBytes()} + */ + @Deprecated + public static byte[] toByteArray(String input) throws IOException { + return input.getBytes(); + } + + /** + * Get the contents of a URI as a byte[]. + * + * @param uri the URI to read + * @return the requested byte array + * @throws NullPointerException if the uri is null + * @throws java.io.IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URI uri) throws IOException { + return IOUtils.toByteArray(uri.toURL()); + } + + /** + * Get the contents of a URL as a byte[]. + * + * @param url the URL to read + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URL url) throws IOException { + URLConnection conn = url.openConnection(); + try { + return IOUtils.toByteArray(conn); + } finally { + close(conn); + } + } + + /** + * Get the contents of a URLConnection as a byte[]. + * + * @param urlConn the URLConnection to read + * @return the requested byte array + * @throws NullPointerException if the urlConn is null + * @throws java.io.IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URLConnection urlConn) throws IOException { + InputStream inputStream = urlConn.getInputStream(); + try { + return IOUtils.toByteArray(inputStream); + } finally { + inputStream.close(); + } + } + + // read char[] + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a character array + * using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static char[] toCharArray(InputStream is) throws IOException { + return toCharArray(is, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a character array + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static char[] toCharArray(InputStream is, Charset encoding) + throws IOException { + CharArrayWriter output = new CharArrayWriter(); + copy(is, output, encoding); + return output.toCharArray(); + } + + /** + * Get the contents of an InputStream as a character array + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static char[] toCharArray(InputStream is, String encoding) throws IOException { + return toCharArray(is, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a character array. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static char[] toCharArray(Reader input) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(input, sw); + return sw.toCharArray(); + } + + // read toString + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a String + * using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static String toString(InputStream input) throws IOException { + return toString(input, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a String + * using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static String toString(InputStream input, Charset encoding) throws IOException { + StringBuilderWriter sw = new StringBuilderWriter(); + copy(input, sw, encoding); + return sw.toString(); + } + + /** + * Get the contents of an InputStream as a String + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + */ + public static String toString(InputStream input, String encoding) + throws IOException { + return toString(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a String. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static String toString(Reader input) throws IOException { + StringBuilderWriter sw = new StringBuilderWriter(); + copy(input, sw); + return sw.toString(); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.1 + */ + public static String toString(URI uri) throws IOException { + return toString(uri, Charset.defaultCharset()); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.3. + */ + public static String toString(URI uri, Charset encoding) throws IOException { + return toString(uri.toURL(), Charsets.toCharset(encoding)); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.1 + */ + public static String toString(URI uri, String encoding) throws IOException { + return toString(uri, Charsets.toCharset(encoding)); + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.1 + */ + public static String toString(URL url) throws IOException { + return toString(url, Charset.defaultCharset()); + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.3 + */ + public static String toString(URL url, Charset encoding) throws IOException { + InputStream inputStream = url.openStream(); + try { + return toString(inputStream, encoding); + } finally { + inputStream.close(); + } + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.1 + */ + public static String toString(URL url, String encoding) throws IOException { + return toString(url, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a byte[] as a String + * using the default character encoding of the platform. + * + * @param input the byte array to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#String(byte[])} + */ + @Deprecated + public static String toString(byte[] input) throws IOException { + return new String(input); + } + + /** + * Get the contents of a byte[] as a String + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + * + * @param input the byte array to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs (never occurs) + */ + public static String toString(byte[] input, String encoding) throws IOException { + return new String(input, encoding); + } + + // readLines + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static List readLines(InputStream input) throws IOException { + return readLines(input, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static List readLines(InputStream input, Charset encoding) throws IOException { + InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(encoding)); + return readLines(reader); + } + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static List readLines(InputStream input, String encoding) throws IOException { + return readLines(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a list of Strings, + * one entry per line. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = reader.readLine(); + while (line != null) { + list.add(line); + line = reader.readLine(); + } + return list; + } + + //----------------------------------------------------------------------- + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the CharSequence to convert + * @return an input stream + * @since 2.0 + */ + public static InputStream toInputStream(CharSequence input) { + return toInputStream(input, Charset.defaultCharset()); + } + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the specified character encoding. + * + * @param input the CharSequence to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @since 2.3 + */ + public static InputStream toInputStream(CharSequence input, Charset encoding) { + return toInputStream(input.toString(), encoding); + } + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + * + * @param input the CharSequence to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @throws java.io.IOException if the encoding is invalid + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.0 + */ + public static InputStream toInputStream(CharSequence input, String encoding) throws IOException { + return toInputStream(input, Charsets.toCharset(encoding)); + } + + //----------------------------------------------------------------------- + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the string to convert + * @return an input stream + * @since 1.1 + */ + public static InputStream toInputStream(String input) { + return toInputStream(input, Charset.defaultCharset()); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @since 2.3 + */ + public static InputStream toInputStream(String input, Charset encoding) { + return new ByteArrayInputStream(StringCodingUtils.getBytes(input, Charsets.toCharset(encoding))); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @throws java.io.IOException if the encoding is invalid + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static InputStream toInputStream(String input, String encoding) throws IOException { + byte[] bytes = StringCodingUtils.getBytes(input,Charsets.toCharset(encoding)); + return new ByteArrayInputStream(bytes); + } + + // write byte[] + //----------------------------------------------------------------------- + + /** + * Writes bytes from a byte[] to an OutputStream. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(byte[] data, OutputStream output) + throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the default character encoding of the platform. + *

+ * This method uses {@link String#String(byte[])}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(byte[] data, Writer output) throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the specified character encoding. + *

+ * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(byte[] data, Writer output, Charset encoding) throws IOException { + if (data != null) { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD){ + output.write(new String(data, Charsets.toCharset(encoding).name())); + }else{ + output.write(new String(data, Charsets.toCharset(encoding))); + } + } + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(byte[] data, Writer output, String encoding) throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write char[] + //----------------------------------------------------------------------- + + /** + * Writes chars from a char[] to a Writer + * using the default character encoding of the platform. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(char[] data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream. + *

+ * This method uses {@link String#String(char[])} and + * {@link String#getBytes()}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(char[] data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream using the specified character encoding. + *

+ * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(char[] data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(StringCodingUtils.getBytes(new String(data), Charsets.toCharset(encoding))); + } + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(char[] data, OutputStream output, String encoding) + throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write CharSequence + //----------------------------------------------------------------------- + + /** + * Writes chars from a CharSequence to a Writer. + * + * @param data the CharSequence to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static void write(CharSequence data, Writer output) throws IOException { + if (data != null) { + write(data.toString(), output); + } + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

+ * This method uses {@link String#getBytes()}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static void write(CharSequence data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the specified character encoding. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(CharSequence data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + write(data.toString(), output, encoding); + } + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.0 + */ + public static void write(CharSequence data, OutputStream output, String encoding) throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write String + //----------------------------------------------------------------------- + + /** + * Writes chars from a String to a Writer. + * + * @param data the String to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(String data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

+ * This method uses {@link String#getBytes()}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(String data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the specified character encoding. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(String data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(StringCodingUtils.getBytes(data, Charsets.toCharset(encoding))); + } + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(String data, OutputStream output, String encoding) + throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write StringBuffer + //----------------------------------------------------------------------- + + /** + * Writes chars from a StringBuffer to a Writer. + * + * @param data the StringBuffer to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + * @deprecated replaced by write(CharSequence, Writer) + */ + @Deprecated + public static void write(StringBuffer data, Writer output) + throws IOException { + if (data != null) { + output.write(data.toString()); + } + } + + /** + * Writes chars from a StringBuffer to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

+ * This method uses {@link String#getBytes()}. + * + * @param data the StringBuffer to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + * @deprecated replaced by write(CharSequence, OutputStream) + */ + @Deprecated + public static void write(StringBuffer data, OutputStream output) + throws IOException { + write(data, output, (String) null); + } + + /** + * Writes chars from a StringBuffer to bytes on an + * OutputStream using the specified character encoding. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link String#getBytes(String)}. + * + * @param data the StringBuffer to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + * @deprecated replaced by write(CharSequence, OutputStream, String) + */ + @Deprecated + public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException { + if (data != null) { + output.write(StringCodingUtils.getBytes(data.toString(), Charsets.toCharset(encoding))); + } + } + + // writeLines + //----------------------------------------------------------------------- + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the default character + * encoding of the platform and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @throws NullPointerException if the output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + OutputStream output) throws IOException { + writeLines(lines, lineEnding, output, Charset.defaultCharset()); + } + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the specified character + * encoding and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void writeLines(Collection lines, String lineEnding, OutputStream output, Charset encoding) + throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + Charset cs = Charsets.toCharset(encoding); + for (Object line : lines) { + if (line != null) { + output.write(StringCodingUtils.getBytes(line.toString(), cs)); + } + output.write(StringCodingUtils.getBytes(lineEnding,cs)); + } + } + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the specified character + * encoding and the specified line ending. + *

+ * Character encoding names can be found at + * IANA. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + OutputStream output, String encoding) throws IOException { + writeLines(lines, lineEnding, output, Charsets.toCharset(encoding)); + } + + /** + * Writes the toString() value of each item in a collection to + * a Writer line by line, using the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param writer the Writer to write to, not null, not closed + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + Writer writer) throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + for (Object line : lines) { + if (line != null) { + writer.write(line.toString()); + } + writer.write(lineEnding); + } + } + + // copy from InputStream + //----------------------------------------------------------------------- + + /** + * Copy bytes from an InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * Large streams (over 2GB) will return a bytes copied value of + * -1 after the copy has completed since the correct + * number of bytes cannot be returned as an int. For large streams + * use the copyLarge(InputStream, OutputStream) method. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(InputStream input, OutputStream output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(InputStream input, OutputStream output) + throws IOException { + return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) + throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy some or all bytes from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input bytes. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param inputOffset : number of bytes to skip from input before copying + * -ve values are ignored + * @param length : number of bytes to copy. -ve means all + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, long inputOffset, long length) + throws IOException { + return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy some or all bytes from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input bytes. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

+ * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param inputOffset : number of bytes to skip from input before copying + * -ve values are ignored + * @param length : number of bytes to copy. -ve means all + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, + final long inputOffset, final long length, byte[] buffer) throws IOException { + if (inputOffset > 0) { + skipFully(input, inputOffset); + } + if (length == 0) { + return 0; + } + final int bufferLength = buffer.length; + int bytesToRead = bufferLength; + if (length > 0 && length < bufferLength) { + bytesToRead = (int) length; + } + int read; + long totalRead = 0; + while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { + output.write(buffer, 0, read); + totalRead += read; + if (length > 0) { // only adjust length if not reading to the end + // Note the cast must work because buffer.length is an integer + bytesToRead = (int) Math.min(length - totalRead, bufferLength); + } + } + return totalRead; + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the default character encoding of the platform. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * This method uses {@link java.io.InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void copy(InputStream input, Writer output) + throws IOException { + copy(input, output, Charset.defaultCharset()); + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * This method uses {@link java.io.InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void copy(InputStream input, Writer output, Charset encoding) throws IOException { + InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(encoding)); + copy(in, output); + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

+ * Character encoding names can be found at + * IANA. + *

+ * This method uses {@link java.io.InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void copy(InputStream input, Writer output, String encoding) throws IOException { + copy(input, output, Charsets.toCharset(encoding)); + } + + // copy from Reader + //----------------------------------------------------------------------- + + /** + * Copy chars from a Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Large streams (over 2GB) will return a chars copied value of + * -1 after the copy has completed since the correct + * number of chars cannot be returned as an int. For large streams + * use the copyLarge(Reader, Writer) method. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(Reader input, Writer output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy chars from a large (over 2GB) Reader to a Writer. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(Reader input, Writer output) throws IOException { + return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy chars from a large (over 2GB) Reader to a Writer. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

+ * + * @param input the Reader to read from + * @param output the Writer to write to + * @param buffer the buffer to be used for the copy + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy some or all chars from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input chars. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @param inputOffset : number of chars to skip from input before copying + * -ve values are ignored + * @param length : number of chars to copy. -ve means all + * @return the number of chars copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length) + throws IOException { + return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy some or all chars from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input chars. + *

+ * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

+ * + * @param input the Reader to read from + * @param output the Writer to write to + * @param inputOffset : number of chars to skip from input before copying + * -ve values are ignored + * @param length : number of chars to copy. -ve means all + * @param buffer the buffer to be used for the copy + * @return the number of chars copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length, char[] buffer) + throws IOException { + if (inputOffset > 0) { + skipFully(input, inputOffset); + } + if (length == 0) { + return 0; + } + int bytesToRead = buffer.length; + if (length > 0 && length < buffer.length) { + bytesToRead = (int) length; + } + int read; + long totalRead = 0; + while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { + output.write(buffer, 0, read); + totalRead += read; + if (length > 0) { // only adjust length if not reading to the end + // Note the cast must work because buffer.length is an integer + bytesToRead = (int) Math.min(length - totalRead, buffer.length); + } + } + return totalRead; + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the default character encoding of the + * platform, and calling flush. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

+ * This method uses {@link java.io.OutputStreamWriter}. + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void copy(Reader input, OutputStream output) + throws IOException { + copy(input, output, Charset.defaultCharset()); + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the specified character encoding, and + * calling flush. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ *

+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

+ *

+ * This method uses {@link java.io.OutputStreamWriter}. + *

+ * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException { + OutputStreamWriter out = new OutputStreamWriter(output, Charsets.toCharset(encoding)); + copy(input, out); + // XXX Unless anyone is planning on rewriting OutputStreamWriter, + // we have to flush here. + out.flush(); + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the specified character encoding, and + * calling flush. + *

+ * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

+ * Character encoding names can be found at + * IANA. + *

+ * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

+ * This method uses {@link java.io.OutputStreamWriter}. + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void copy(Reader input, OutputStream output, String encoding) throws IOException { + copy(input, output, Charsets.toCharset(encoding)); + } + + // content equals + //----------------------------------------------------------------------- + + /** + * Compare the contents of two Streams to determine if they are equal or + * not. + *

+ * This method buffers the input internally using + * BufferedInputStream if they are not already buffered. + * + * @param input1 the first stream + * @param input2 the second stream + * @return true if the content of the streams are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static boolean contentEquals(InputStream input1, InputStream input2) + throws IOException { + if (!(input1 instanceof BufferedInputStream)) { + input1 = new BufferedInputStream(input1); + } + if (!(input2 instanceof BufferedInputStream)) { + input2 = new BufferedInputStream(input2); + } + + int ch = input1.read(); + while (EOF != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == EOF; + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not. + *

+ * This method buffers the input internally using + * BufferedReader if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static boolean contentEquals(Reader input1, Reader input2) + throws IOException { + + input1 = toBufferedReader(input1); + input2 = toBufferedReader(input2); + + int ch = input1.read(); + while (EOF != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == EOF; + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not, ignoring EOL characters. + *

+ * This method buffers the input internally using + * BufferedReader if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise + * @throws NullPointerException if either input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) + throws IOException { + BufferedReader br1 = toBufferedReader(input1); + BufferedReader br2 = toBufferedReader(input2); + + String line1 = br1.readLine(); + String line2 = br2.readLine(); + while (line1 != null && line2 != null && line1.equals(line2)) { + line1 = br1.readLine(); + line2 = br2.readLine(); + } + return line1 == null ? line2 == null ? true : false : line1.equals(line2); + } + + /** + * Skip bytes from an input byte stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input byte stream to skip + * @param toSkip number of bytes to skip. + * @return number of bytes actually skipped. + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see java.io.InputStream#skip(long) + * @since 2.0 + */ + public static long skip(InputStream input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_BYTE_BUFFER == null) { + SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE]; + } + long remain = toSkip; + while (remain > 0) { + long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skip characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input character stream to skip + * @param toSkip number of characters to skip. + * @return number of characters actually skipped. + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see java.io.Reader#skip(long) + * @since 2.0 + */ + public static long skip(Reader input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_CHAR_BUFFER == null) { + SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE]; + } + long remain = toSkip; + while (remain > 0) { + long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skip the requested number of bytes or fail if there are not enough left. + *

+ * This allows for the possibility that {@link java.io.InputStream#skip(long)} may + * not skip as many bytes as requested (most likely because of reaching EOF). + * + * @param input stream to skip + * @param toSkip the number of bytes to skip + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws java.io.EOFException if the number of bytes skipped was incorrect + * @see java.io.InputStream#skip(long) + * @since 2.0 + */ + public static void skipFully(InputStream input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); + } + long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); + } + } + + /** + * Skip the requested number of characters or fail if there are not enough left. + *

+ * This allows for the possibility that {@link java.io.Reader#skip(long)} may + * not skip as many characters as requested (most likely because of reaching EOF). + * + * @param input stream to skip + * @param toSkip the number of characters to skip + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws java.io.EOFException if the number of characters skipped was incorrect + * @see java.io.Reader#skip(long) + * @since 2.0 + */ + public static void skipFully(Reader input, long toSkip) throws IOException { + long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped); + } + } + + + /** + * Read characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(Reader input, char[] buffer, int offset, int length) throws IOException { + if (length < 0) { + throw new IllegalArgumentException("Length must not be negative: " + length); + } + int remaining = length; + while (remaining > 0) { + int location = length - remaining; + int count = input.read(buffer, offset + location, remaining); + if (EOF == count) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Read characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(Reader input, char[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read bytes from an input stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.InputStream}. + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException { + if (length < 0) { + throw new IllegalArgumentException("Length must not be negative: " + length); + } + int remaining = length; + while (remaining > 0) { + int location = length - remaining; + int count = input.read(buffer, offset + location, remaining); + if (EOF == count) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Read bytes from an input stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.InputStream}. + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(InputStream input, byte[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of characters or fail if there are not enough left. + *

+ * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may + * not read as many characters as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of characters read was incorrect + * @since 2.2 + */ + public static void readFully(Reader input, char[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Length to read: " + length + " actual: " + actual); + } + } + + /** + * Read the requested number of characters or fail if there are not enough left. + *

+ * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may + * not read as many characters as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of characters read was incorrect + * @since 2.2 + */ + public static void readFully(Reader input, char[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + *

+ * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may + * not read as many bytes as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of bytes read was incorrect + * @since 2.2 + */ + public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Length to read: " + length + " actual: " + actual); + } + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + *

+ * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may + * not read as many bytes as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of bytes read was incorrect + * @since 2.2 + */ + public static void readFully(InputStream input, byte[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } +} diff --git a/src/com/litesuits/common/io/StringCodingUtils.java b/app/src/main/java/com/litesuits/common/io/StringCodingUtils.java similarity index 100% rename from src/com/litesuits/common/io/StringCodingUtils.java rename to app/src/main/java/com/litesuits/common/io/StringCodingUtils.java diff --git a/src/com/litesuits/common/io/stream/ByteArrayOutputStream.java b/app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java similarity index 97% rename from src/com/litesuits/common/io/stream/ByteArrayOutputStream.java rename to app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java index 0e9e47b..6202023 100644 --- a/src/com/litesuits/common/io/stream/ByteArrayOutputStream.java +++ b/app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java @@ -1,357 +1,357 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io.stream; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.SequenceInputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * This class implements an output stream in which the data is - * written into a byte array. The buffer automatically grows as data - * is written to it. - *

- * The data can be retrieved using toByteArray() and - * toString(). - *

- * Closing a ByteArrayOutputStream has no effect. The methods in - * this class can be called after the stream has been closed without - * generating an IOException. - *

- * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} - * class. The original implementation only allocates 32 bytes at the beginning. - * As this class is designed for heavy duty it starts at 1024 bytes. In contrast - * to the original it doesn't reallocate the whole memory block but allocates - * additional buffers. This way no buffers need to be garbage collected and - * the contents don't have to be copied to the new buffer. This class is - * designed to behave exactly like the original. The only exception is the - * deprecated toString(int) method that has been ignored. - * - * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $ - */ -public class ByteArrayOutputStream extends OutputStream { - - /** A singleton empty byte array. */ - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - /** The list of buffers, which grows and never reduces. */ - private final List buffers = new ArrayList(); - /** The index of the current buffer. */ - private int currentBufferIndex; - /** The total count of bytes in all the filled buffers. */ - private int filledBufferSum; - /** The current buffer. */ - private byte[] currentBuffer; - /** The total count of bytes written. */ - private int count; - - /** - * Creates a new byte array output stream. The buffer capacity is - * initially 1024 bytes, though its size increases if necessary. - */ - public ByteArrayOutputStream() { - this(1024); - } - - /** - * Creates a new byte array output stream, with a buffer capacity of - * the specified size, in bytes. - * - * @param size the initial size - * @throws IllegalArgumentException if size is negative - */ - public ByteArrayOutputStream(int size) { - if (size < 0) { - throw new IllegalArgumentException( - "Negative initial size: " + size); - } - synchronized (this) { - needNewBuffer(size); - } - } - - /** - * Makes a new buffer available either by allocating - * a new one or re-cycling an existing one. - * - * @param newcount the size of the buffer if one is created - */ - private void needNewBuffer(int newcount) { - if (currentBufferIndex < buffers.size() - 1) { - //Recycling old buffer - filledBufferSum += currentBuffer.length; - - currentBufferIndex++; - currentBuffer = buffers.get(currentBufferIndex); - } else { - //Creating new buffer - int newBufferSize; - if (currentBuffer == null) { - newBufferSize = newcount; - filledBufferSum = 0; - } else { - newBufferSize = Math.max( - currentBuffer.length << 1, - newcount - filledBufferSum); - filledBufferSum += currentBuffer.length; - } - - currentBufferIndex++; - currentBuffer = new byte[newBufferSize]; - buffers.add(currentBuffer); - } - } - - /** - * Write the bytes to byte array. - * @param b the bytes to write - * @param off The start offset - * @param len The number of bytes to write - */ - @Override - public void write(byte[] b, int off, int len) { - if ((off < 0) - || (off > b.length) - || (len < 0) - || ((off + len) > b.length) - || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return; - } - synchronized (this) { - int newcount = count + len; - int remaining = len; - int inBufferPos = count - filledBufferSum; - while (remaining > 0) { - int part = Math.min(remaining, currentBuffer.length - inBufferPos); - System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); - remaining -= part; - if (remaining > 0) { - needNewBuffer(newcount); - inBufferPos = 0; - } - } - count = newcount; - } - } - - /** - * Write a byte to byte array. - * @param b the byte to write - */ - @Override - public synchronized void write(int b) { - int inBufferPos = count - filledBufferSum; - if (inBufferPos == currentBuffer.length) { - needNewBuffer(count + 1); - inBufferPos = 0; - } - currentBuffer[inBufferPos] = (byte) b; - count++; - } - - /** - * Writes the entire contents of the specified input stream to this - * byte stream. Bytes from the input stream are read directly into the - * internal buffers of this streams. - * - * @param in the input stream to read from - * @return total number of bytes read from the input stream - * (and written to this stream) - * @throws java.io.IOException if an I/O error occurs while reading the input stream - * @since 1.4 - */ - public synchronized int write(InputStream in) throws IOException { - int readCount = 0; - int inBufferPos = count - filledBufferSum; - int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); - while (n != -1) { - readCount += n; - inBufferPos += n; - count += n; - if (inBufferPos == currentBuffer.length) { - needNewBuffer(currentBuffer.length); - inBufferPos = 0; - } - n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); - } - return readCount; - } - - /** - * Return the current size of the byte array. - * @return the current size of the byte array - */ - public synchronized int size() { - return count; - } - - /** - * Closing a ByteArrayOutputStream has no effect. The methods in - * this class can be called after the stream has been closed without - * generating an IOException. - * - * @throws java.io.IOException never (this method should not declare this exception - * but it has to now due to backwards compatability) - */ - @Override - public void close() throws IOException { - //nop - } - - /** - * @see java.io.ByteArrayOutputStream#reset() - */ - public synchronized void reset() { - count = 0; - filledBufferSum = 0; - currentBufferIndex = 0; - currentBuffer = buffers.get(currentBufferIndex); - } - - /** - * Writes the entire contents of this byte stream to the - * specified output stream. - * - * @param out the output stream to write to - * @throws java.io.IOException if an I/O error occurs, such as if the stream is closed - * @see java.io.ByteArrayOutputStream#writeTo(java.io.OutputStream) - */ - public synchronized void writeTo(OutputStream out) throws IOException { - int remaining = count; - for (byte[] buf : buffers) { - int c = Math.min(buf.length, remaining); - out.write(buf, 0, c); - remaining -= c; - if (remaining == 0) { - break; - } - } - } - - /** - * Fetches entire contents of an InputStream and represent - * same data as result InputStream. - *

- * This method is useful where, - *

    - *
  • Source InputStream is slow.
  • - *
  • It has network resources associated, so we cannot keep it open for - * long time.
  • - *
  • It has network timeout associated.
  • - *
- * It can be used in favor of {@link #toByteArray()}, since it - * avoids unnecessary allocation and copy of byte[].
- * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input Stream to be fully buffered. - * @return A fully buffered stream. - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static InputStream toBufferedInputStream(InputStream input) - throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - output.write(input); - return output.toBufferedInputStream(); - } - - /** - * Gets the current contents of this byte stream as a Input Stream. The - * returned stream is backed by buffers of this stream, - * avoiding memory allocation and copy, thus saving space and time.
- * - * @return the current contents of this output stream. - * @see java.io.ByteArrayOutputStream#toByteArray() - * @see #reset() - * @since 2.0 - */ - private InputStream toBufferedInputStream() { - int remaining = count; - if (remaining == 0) { - return new ClosedInputStream(); - } - List list = new ArrayList(buffers.size()); - for (byte[] buf : buffers) { - int c = Math.min(buf.length, remaining); - list.add(new ByteArrayInputStream(buf, 0, c)); - remaining -= c; - if (remaining == 0) { - break; - } - } - return new SequenceInputStream(Collections.enumeration(list)); - } - - /** - * Gets the curent contents of this byte stream as a byte array. - * The result is independent of this stream. - * - * @return the current contents of this output stream, as a byte array - * @see java.io.ByteArrayOutputStream#toByteArray() - */ - public synchronized byte[] toByteArray() { - int remaining = count; - if (remaining == 0) { - return EMPTY_BYTE_ARRAY; - } - byte newbuf[] = new byte[remaining]; - int pos = 0; - for (byte[] buf : buffers) { - int c = Math.min(buf.length, remaining); - System.arraycopy(buf, 0, newbuf, pos, c); - pos += c; - remaining -= c; - if (remaining == 0) { - break; - } - } - return newbuf; - } - - /** - * Gets the curent contents of this byte stream as a string. - * @return the contents of the byte array as a String - * @see java.io.ByteArrayOutputStream#toString() - */ - @Override - public String toString() { - return new String(toByteArray()); - } - - /** - * Gets the curent contents of this byte stream as a string - * using the specified encoding. - * - * @param enc the name of the character encoding - * @return the string converted from the byte array - * @throws java.io.UnsupportedEncodingException if the encoding is not supported - * @see java.io.ByteArrayOutputStream#toString(String) - */ - public String toString(String enc) throws UnsupportedEncodingException { - return new String(toByteArray(), enc); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io.stream; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class implements an output stream in which the data is + * written into a byte array. The buffer automatically grows as data + * is written to it. + *

+ * The data can be retrieved using toByteArray() and + * toString(). + *

+ * Closing a ByteArrayOutputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + *

+ * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} + * class. The original implementation only allocates 32 bytes at the beginning. + * As this class is designed for heavy duty it starts at 1024 bytes. In contrast + * to the original it doesn't reallocate the whole memory block but allocates + * additional buffers. This way no buffers need to be garbage collected and + * the contents don't have to be copied to the new buffer. This class is + * designed to behave exactly like the original. The only exception is the + * deprecated toString(int) method that has been ignored. + * + * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $ + */ +public class ByteArrayOutputStream extends OutputStream { + + /** A singleton empty byte array. */ + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** The list of buffers, which grows and never reduces. */ + private final List buffers = new ArrayList(); + /** The index of the current buffer. */ + private int currentBufferIndex; + /** The total count of bytes in all the filled buffers. */ + private int filledBufferSum; + /** The current buffer. */ + private byte[] currentBuffer; + /** The total count of bytes written. */ + private int count; + + /** + * Creates a new byte array output stream. The buffer capacity is + * initially 1024 bytes, though its size increases if necessary. + */ + public ByteArrayOutputStream() { + this(1024); + } + + /** + * Creates a new byte array output stream, with a buffer capacity of + * the specified size, in bytes. + * + * @param size the initial size + * @throws IllegalArgumentException if size is negative + */ + public ByteArrayOutputStream(int size) { + if (size < 0) { + throw new IllegalArgumentException( + "Negative initial size: " + size); + } + synchronized (this) { + needNewBuffer(size); + } + } + + /** + * Makes a new buffer available either by allocating + * a new one or re-cycling an existing one. + * + * @param newcount the size of the buffer if one is created + */ + private void needNewBuffer(int newcount) { + if (currentBufferIndex < buffers.size() - 1) { + //Recycling old buffer + filledBufferSum += currentBuffer.length; + + currentBufferIndex++; + currentBuffer = buffers.get(currentBufferIndex); + } else { + //Creating new buffer + int newBufferSize; + if (currentBuffer == null) { + newBufferSize = newcount; + filledBufferSum = 0; + } else { + newBufferSize = Math.max( + currentBuffer.length << 1, + newcount - filledBufferSum); + filledBufferSum += currentBuffer.length; + } + + currentBufferIndex++; + currentBuffer = new byte[newBufferSize]; + buffers.add(currentBuffer); + } + } + + /** + * Write the bytes to byte array. + * @param b the bytes to write + * @param off The start offset + * @param len The number of bytes to write + */ + @Override + public void write(byte[] b, int off, int len) { + if ((off < 0) + || (off > b.length) + || (len < 0) + || ((off + len) > b.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + synchronized (this) { + int newcount = count + len; + int remaining = len; + int inBufferPos = count - filledBufferSum; + while (remaining > 0) { + int part = Math.min(remaining, currentBuffer.length - inBufferPos); + System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); + remaining -= part; + if (remaining > 0) { + needNewBuffer(newcount); + inBufferPos = 0; + } + } + count = newcount; + } + } + + /** + * Write a byte to byte array. + * @param b the byte to write + */ + @Override + public synchronized void write(int b) { + int inBufferPos = count - filledBufferSum; + if (inBufferPos == currentBuffer.length) { + needNewBuffer(count + 1); + inBufferPos = 0; + } + currentBuffer[inBufferPos] = (byte) b; + count++; + } + + /** + * Writes the entire contents of the specified input stream to this + * byte stream. Bytes from the input stream are read directly into the + * internal buffers of this streams. + * + * @param in the input stream to read from + * @return total number of bytes read from the input stream + * (and written to this stream) + * @throws java.io.IOException if an I/O error occurs while reading the input stream + * @since 1.4 + */ + public synchronized int write(InputStream in) throws IOException { + int readCount = 0; + int inBufferPos = count - filledBufferSum; + int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); + while (n != -1) { + readCount += n; + inBufferPos += n; + count += n; + if (inBufferPos == currentBuffer.length) { + needNewBuffer(currentBuffer.length); + inBufferPos = 0; + } + n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); + } + return readCount; + } + + /** + * Return the current size of the byte array. + * @return the current size of the byte array + */ + public synchronized int size() { + return count; + } + + /** + * Closing a ByteArrayOutputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + * + * @throws java.io.IOException never (this method should not declare this exception + * but it has to now due to backwards compatability) + */ + @Override + public void close() throws IOException { + //nop + } + + /** + * @see java.io.ByteArrayOutputStream#reset() + */ + public synchronized void reset() { + count = 0; + filledBufferSum = 0; + currentBufferIndex = 0; + currentBuffer = buffers.get(currentBufferIndex); + } + + /** + * Writes the entire contents of this byte stream to the + * specified output stream. + * + * @param out the output stream to write to + * @throws java.io.IOException if an I/O error occurs, such as if the stream is closed + * @see java.io.ByteArrayOutputStream#writeTo(java.io.OutputStream) + */ + public synchronized void writeTo(OutputStream out) throws IOException { + int remaining = count; + for (byte[] buf : buffers) { + int c = Math.min(buf.length, remaining); + out.write(buf, 0, c); + remaining -= c; + if (remaining == 0) { + break; + } + } + } + + /** + * Fetches entire contents of an InputStream and represent + * same data as result InputStream. + *

+ * This method is useful where, + *

    + *
  • Source InputStream is slow.
  • + *
  • It has network resources associated, so we cannot keep it open for + * long time.
  • + *
  • It has network timeout associated.
  • + *
+ * It can be used in favor of {@link #toByteArray()}, since it + * avoids unnecessary allocation and copy of byte[].
+ * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input Stream to be fully buffered. + * @return A fully buffered stream. + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static InputStream toBufferedInputStream(InputStream input) + throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(input); + return output.toBufferedInputStream(); + } + + /** + * Gets the current contents of this byte stream as a Input Stream. The + * returned stream is backed by buffers of this stream, + * avoiding memory allocation and copy, thus saving space and time.
+ * + * @return the current contents of this output stream. + * @see java.io.ByteArrayOutputStream#toByteArray() + * @see #reset() + * @since 2.0 + */ + private InputStream toBufferedInputStream() { + int remaining = count; + if (remaining == 0) { + return new ClosedInputStream(); + } + List list = new ArrayList(buffers.size()); + for (byte[] buf : buffers) { + int c = Math.min(buf.length, remaining); + list.add(new ByteArrayInputStream(buf, 0, c)); + remaining -= c; + if (remaining == 0) { + break; + } + } + return new SequenceInputStream(Collections.enumeration(list)); + } + + /** + * Gets the curent contents of this byte stream as a byte array. + * The result is independent of this stream. + * + * @return the current contents of this output stream, as a byte array + * @see java.io.ByteArrayOutputStream#toByteArray() + */ + public synchronized byte[] toByteArray() { + int remaining = count; + if (remaining == 0) { + return EMPTY_BYTE_ARRAY; + } + byte newbuf[] = new byte[remaining]; + int pos = 0; + for (byte[] buf : buffers) { + int c = Math.min(buf.length, remaining); + System.arraycopy(buf, 0, newbuf, pos, c); + pos += c; + remaining -= c; + if (remaining == 0) { + break; + } + } + return newbuf; + } + + /** + * Gets the curent contents of this byte stream as a string. + * @return the contents of the byte array as a String + * @see java.io.ByteArrayOutputStream#toString() + */ + @Override + public String toString() { + return new String(toByteArray()); + } + + /** + * Gets the curent contents of this byte stream as a string + * using the specified encoding. + * + * @param enc the name of the character encoding + * @return the string converted from the byte array + * @throws java.io.UnsupportedEncodingException if the encoding is not supported + * @see java.io.ByteArrayOutputStream#toString(String) + */ + public String toString(String enc) throws UnsupportedEncodingException { + return new String(toByteArray(), enc); + } + +} diff --git a/src/com/litesuits/common/io/stream/ClosedInputStream.java b/app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java similarity index 97% rename from src/com/litesuits/common/io/stream/ClosedInputStream.java rename to app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java index a44a029..0739e79 100644 --- a/src/com/litesuits/common/io/stream/ClosedInputStream.java +++ b/app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java @@ -1,49 +1,49 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io.stream; - -import java.io.InputStream; - -/** - * Closed input stream. This stream returns -1 to all attempts to read - * something from the stream. - *

- * Typically uses of this class include testing for corner cases in methods - * that accept input streams and acting as a sentinel value instead of a - * {@code null} input stream. - * - * @version $Id: ClosedInputStream.java 1307459 2012-03-30 15:11:44Z ggregory $ - * @since 1.4 - */ -public class ClosedInputStream extends InputStream { - - /** - * A singleton. - */ - public static final ClosedInputStream CLOSED_INPUT_STREAM = new ClosedInputStream(); - - /** - * Returns -1 to indicate that the stream is closed. - * - * @return always -1 - */ - @Override - public int read() { - return -1; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io.stream; + +import java.io.InputStream; + +/** + * Closed input stream. This stream returns -1 to all attempts to read + * something from the stream. + *

+ * Typically uses of this class include testing for corner cases in methods + * that accept input streams and acting as a sentinel value instead of a + * {@code null} input stream. + * + * @version $Id: ClosedInputStream.java 1307459 2012-03-30 15:11:44Z ggregory $ + * @since 1.4 + */ +public class ClosedInputStream extends InputStream { + + /** + * A singleton. + */ + public static final ClosedInputStream CLOSED_INPUT_STREAM = new ClosedInputStream(); + + /** + * Returns -1 to indicate that the stream is closed. + * + * @return always -1 + */ + @Override + public int read() { + return -1; + } + +} diff --git a/src/com/litesuits/common/io/stream/StringBuilderWriter.java b/app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java similarity index 96% rename from src/com/litesuits/common/io/stream/StringBuilderWriter.java rename to app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java index 2016117..99e148a 100644 --- a/src/com/litesuits/common/io/stream/StringBuilderWriter.java +++ b/app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java @@ -1,160 +1,160 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io.stream; - -import java.io.Serializable; -import java.io.Writer; - -/** - * {@link java.io.Writer} implementation that outputs to a {@link StringBuilder}. - *

- * NOTE: This implementation, as an alternative to - * java.io.StringWriter, provides an un-synchronized - * (i.e. for use in a single thread) implementation for better performance. - * For safe usage with multiple {@link Thread}s then - * java.io.StringWriter should be used. - * - * @version $Id: StringBuilderWriter.java 1304052 2012-03-22 20:55:29Z ggregory $ - * @since 2.0 - */ -public class StringBuilderWriter extends Writer implements Serializable { - - private final StringBuilder builder; - - /** - * Construct a new {@link StringBuilder} instance with default capacity. - */ - public StringBuilderWriter() { - this.builder = new StringBuilder(); - } - - /** - * Construct a new {@link StringBuilder} instance with the specified capacity. - * - * @param capacity The initial capacity of the underlying {@link StringBuilder} - */ - public StringBuilderWriter(int capacity) { - this.builder = new StringBuilder(capacity); - } - - /** - * Construct a new instance with the specified {@link StringBuilder}. - * - * @param builder The String builder - */ - public StringBuilderWriter(StringBuilder builder) { - this.builder = builder != null ? builder : new StringBuilder(); - } - - /** - * Append a single character to this Writer. - * - * @param value The character to append - * @return This writer instance - */ - @Override - public Writer append(char value) { - builder.append(value); - return this; - } - - /** - * Append a character sequence to this Writer. - * - * @param value The character to append - * @return This writer instance - */ - @Override - public Writer append(CharSequence value) { - builder.append(value); - return this; - } - - /** - * Append a portion of a character sequence to the {@link StringBuilder}. - * - * @param value The character to append - * @param start The index of the first character - * @param end The index of the last character + 1 - * @return This writer instance - */ - @Override - public Writer append(CharSequence value, int start, int end) { - builder.append(value, start, end); - return this; - } - - /** - * Closing this writer has no effect. - */ - @Override - public void close() { - } - - /** - * Flushing this writer has no effect. - */ - @Override - public void flush() { - } - - - /** - * Write a String to the {@link StringBuilder}. - * - * @param value The value to write - */ - @Override - public void write(String value) { - if (value != null) { - builder.append(value); - } - } - - /** - * Write a portion of a character array to the {@link StringBuilder}. - * - * @param value The value to write - * @param offset The index of the first character - * @param length The number of characters to write - */ - @Override - public void write(char[] value, int offset, int length) { - if (value != null) { - builder.append(value, offset, length); - } - } - - /** - * Return the underlying builder. - * - * @return The underlying builder - */ - public StringBuilder getBuilder() { - return builder; - } - - /** - * Returns {@link StringBuilder#toString()}. - * - * @return The contents of the String builder. - */ - @Override - public String toString() { - return builder.toString(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io.stream; + +import java.io.Serializable; +import java.io.Writer; + +/** + * {@link java.io.Writer} implementation that outputs to a {@link StringBuilder}. + *

+ * NOTE: This implementation, as an alternative to + * java.io.StringWriter, provides an un-synchronized + * (i.e. for use in a single thread) implementation for better performance. + * For safe usage with multiple {@link Thread}s then + * java.io.StringWriter should be used. + * + * @version $Id: StringBuilderWriter.java 1304052 2012-03-22 20:55:29Z ggregory $ + * @since 2.0 + */ +public class StringBuilderWriter extends Writer implements Serializable { + + private final StringBuilder builder; + + /** + * Construct a new {@link StringBuilder} instance with default capacity. + */ + public StringBuilderWriter() { + this.builder = new StringBuilder(); + } + + /** + * Construct a new {@link StringBuilder} instance with the specified capacity. + * + * @param capacity The initial capacity of the underlying {@link StringBuilder} + */ + public StringBuilderWriter(int capacity) { + this.builder = new StringBuilder(capacity); + } + + /** + * Construct a new instance with the specified {@link StringBuilder}. + * + * @param builder The String builder + */ + public StringBuilderWriter(StringBuilder builder) { + this.builder = builder != null ? builder : new StringBuilder(); + } + + /** + * Append a single character to this Writer. + * + * @param value The character to append + * @return This writer instance + */ + @Override + public Writer append(char value) { + builder.append(value); + return this; + } + + /** + * Append a character sequence to this Writer. + * + * @param value The character to append + * @return This writer instance + */ + @Override + public Writer append(CharSequence value) { + builder.append(value); + return this; + } + + /** + * Append a portion of a character sequence to the {@link StringBuilder}. + * + * @param value The character to append + * @param start The index of the first character + * @param end The index of the last character + 1 + * @return This writer instance + */ + @Override + public Writer append(CharSequence value, int start, int end) { + builder.append(value, start, end); + return this; + } + + /** + * Closing this writer has no effect. + */ + @Override + public void close() { + } + + /** + * Flushing this writer has no effect. + */ + @Override + public void flush() { + } + + + /** + * Write a String to the {@link StringBuilder}. + * + * @param value The value to write + */ + @Override + public void write(String value) { + if (value != null) { + builder.append(value); + } + } + + /** + * Write a portion of a character array to the {@link StringBuilder}. + * + * @param value The value to write + * @param offset The index of the first character + * @param length The number of characters to write + */ + @Override + public void write(char[] value, int offset, int length) { + if (value != null) { + builder.append(value, offset, length); + } + } + + /** + * Return the underlying builder. + * + * @return The underlying builder + */ + public StringBuilder getBuilder() { + return builder; + } + + /** + * Returns {@link StringBuilder#toString()}. + * + * @return The contents of the String builder. + */ + @Override + public String toString() { + return builder.toString(); + } +} diff --git a/src/com/litesuits/common/receiver/PhoneReceiver.java b/app/src/main/java/com/litesuits/common/receiver/PhoneReceiver.java similarity index 100% rename from src/com/litesuits/common/receiver/PhoneReceiver.java rename to app/src/main/java/com/litesuits/common/receiver/PhoneReceiver.java diff --git a/src/com/litesuits/common/receiver/ScreenReceiver.java b/app/src/main/java/com/litesuits/common/receiver/ScreenReceiver.java similarity index 100% rename from src/com/litesuits/common/receiver/ScreenReceiver.java rename to app/src/main/java/com/litesuits/common/receiver/ScreenReceiver.java diff --git a/src/com/litesuits/common/receiver/SmsReceiver.java b/app/src/main/java/com/litesuits/common/receiver/SmsReceiver.java similarity index 100% rename from src/com/litesuits/common/receiver/SmsReceiver.java rename to app/src/main/java/com/litesuits/common/receiver/SmsReceiver.java diff --git a/src/com/litesuits/common/receiver/TimeReceiver.java b/app/src/main/java/com/litesuits/common/receiver/TimeReceiver.java similarity index 100% rename from src/com/litesuits/common/receiver/TimeReceiver.java rename to app/src/main/java/com/litesuits/common/receiver/TimeReceiver.java diff --git a/src/com/litesuits/common/service/NotificationService.java b/app/src/main/java/com/litesuits/common/service/NotificationService.java similarity index 100% rename from src/com/litesuits/common/service/NotificationService.java rename to app/src/main/java/com/litesuits/common/service/NotificationService.java diff --git a/src/com/litesuits/common/utils/AlarmUtil.java b/app/src/main/java/com/litesuits/common/utils/AlarmUtil.java similarity index 100% rename from src/com/litesuits/common/utils/AlarmUtil.java rename to app/src/main/java/com/litesuits/common/utils/AlarmUtil.java diff --git a/src/com/litesuits/common/utils/AndroidUtil.java b/app/src/main/java/com/litesuits/common/utils/AndroidUtil.java similarity index 100% rename from src/com/litesuits/common/utils/AndroidUtil.java rename to app/src/main/java/com/litesuits/common/utils/AndroidUtil.java diff --git a/src/com/litesuits/common/utils/AppUtil.java b/app/src/main/java/com/litesuits/common/utils/AppUtil.java similarity index 100% rename from src/com/litesuits/common/utils/AppUtil.java rename to app/src/main/java/com/litesuits/common/utils/AppUtil.java diff --git a/src/com/litesuits/common/utils/BitmapUtil.java b/app/src/main/java/com/litesuits/common/utils/BitmapUtil.java similarity index 100% rename from src/com/litesuits/common/utils/BitmapUtil.java rename to app/src/main/java/com/litesuits/common/utils/BitmapUtil.java diff --git a/src/com/litesuits/common/utils/ByteUtil.java b/app/src/main/java/com/litesuits/common/utils/ByteUtil.java similarity index 100% rename from src/com/litesuits/common/utils/ByteUtil.java rename to app/src/main/java/com/litesuits/common/utils/ByteUtil.java diff --git a/src/com/litesuits/common/utils/ClassUtil.java b/app/src/main/java/com/litesuits/common/utils/ClassUtil.java similarity index 96% rename from src/com/litesuits/common/utils/ClassUtil.java rename to app/src/main/java/com/litesuits/common/utils/ClassUtil.java index 6742f77..bcbd760 100755 --- a/src/com/litesuits/common/utils/ClassUtil.java +++ b/app/src/main/java/com/litesuits/common/utils/ClassUtil.java @@ -1,71 +1,71 @@ -package com.litesuits.common.utils; - -import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.Date; - -/** - * 类工具 - * - * @author mty - * @date 2013-6-10下午8:00:46 - */ -public class ClassUtil { - - /** - * 判断类是否是基础数据类型 - * 目前支持11种 - * - * @param clazz - * @return - */ - public static boolean isBaseDataType(Class clazz) { - return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Boolean.class) - || clazz.equals(Integer.class) || clazz.equals(Long.class) || clazz.equals(Float.class) - || clazz.equals(Double.class) || clazz.equals(Byte.class) || clazz.equals(Character.class) - || clazz.equals(Short.class) || clazz.equals(Date.class) || clazz.equals(byte[].class) - || clazz.equals(Byte[].class); - } - - /** - * 根据类获取对象:不再必须一个无参构造 - * - * @param claxx - * @return - * @throws Exception - */ - public static T newInstance(Class claxx) throws Exception { - Constructor[] cons = claxx.getDeclaredConstructors(); - for (Constructor c : cons) { - Class[] cls = c.getParameterTypes(); - if (cls.length == 0) { - c.setAccessible(true); - return (T) c.newInstance(); - } else { - Object[] objs = new Object[cls.length]; - for (int i = 0; i < cls.length; i++) { - objs[i] = getDefaultPrimiticeValue(cls[i]); - } - c.setAccessible(true); - return (T) c.newInstance(objs); - } - } - return null; - } - - public static Object getDefaultPrimiticeValue(Class clazz) { - if (clazz.isPrimitive()) { - return clazz == boolean.class ? false : 0; - } - return null; - } - - public static boolean isCollection(Class claxx) { - return Collection.class.isAssignableFrom(claxx); - } - - public static boolean isArray(Class claxx) { - return claxx.isArray(); - } - -} +package com.litesuits.common.utils; + +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Date; + +/** + * 类工具 + * + * @author mty + * @date 2013-6-10下午8:00:46 + */ +public class ClassUtil { + + /** + * 判断类是否是基础数据类型 + * 目前支持11种 + * + * @param clazz + * @return + */ + public static boolean isBaseDataType(Class clazz) { + return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Boolean.class) + || clazz.equals(Integer.class) || clazz.equals(Long.class) || clazz.equals(Float.class) + || clazz.equals(Double.class) || clazz.equals(Byte.class) || clazz.equals(Character.class) + || clazz.equals(Short.class) || clazz.equals(Date.class) || clazz.equals(byte[].class) + || clazz.equals(Byte[].class); + } + + /** + * 根据类获取对象:不再必须一个无参构造 + * + * @param claxx + * @return + * @throws Exception + */ + public static T newInstance(Class claxx) throws Exception { + Constructor[] cons = claxx.getDeclaredConstructors(); + for (Constructor c : cons) { + Class[] cls = c.getParameterTypes(); + if (cls.length == 0) { + c.setAccessible(true); + return (T) c.newInstance(); + } else { + Object[] objs = new Object[cls.length]; + for (int i = 0; i < cls.length; i++) { + objs[i] = getDefaultPrimiticeValue(cls[i]); + } + c.setAccessible(true); + return (T) c.newInstance(objs); + } + } + return null; + } + + public static Object getDefaultPrimiticeValue(Class clazz) { + if (clazz.isPrimitive()) { + return clazz == boolean.class ? false : 0; + } + return null; + } + + public static boolean isCollection(Class claxx) { + return Collection.class.isAssignableFrom(claxx); + } + + public static boolean isArray(Class claxx) { + return claxx.isArray(); + } + +} diff --git a/src/com/litesuits/common/utils/ClipboardUtil.java b/app/src/main/java/com/litesuits/common/utils/ClipboardUtil.java similarity index 100% rename from src/com/litesuits/common/utils/ClipboardUtil.java rename to app/src/main/java/com/litesuits/common/utils/ClipboardUtil.java diff --git a/src/com/litesuits/common/utils/CpuUtil.java b/app/src/main/java/com/litesuits/common/utils/CpuUtil.java similarity index 100% rename from src/com/litesuits/common/utils/CpuUtil.java rename to app/src/main/java/com/litesuits/common/utils/CpuUtil.java diff --git a/src/com/litesuits/common/utils/DialogUtil.java b/app/src/main/java/com/litesuits/common/utils/DialogUtil.java similarity index 100% rename from src/com/litesuits/common/utils/DialogUtil.java rename to app/src/main/java/com/litesuits/common/utils/DialogUtil.java diff --git a/src/com/litesuits/common/utils/DisplayUtil.java b/app/src/main/java/com/litesuits/common/utils/DisplayUtil.java similarity index 100% rename from src/com/litesuits/common/utils/DisplayUtil.java rename to app/src/main/java/com/litesuits/common/utils/DisplayUtil.java diff --git a/src/com/litesuits/common/utils/FieldUtil.java b/app/src/main/java/com/litesuits/common/utils/FieldUtil.java similarity index 96% rename from src/com/litesuits/common/utils/FieldUtil.java rename to app/src/main/java/com/litesuits/common/utils/FieldUtil.java index ddd77d6..dedd0cf 100755 --- a/src/com/litesuits/common/utils/FieldUtil.java +++ b/app/src/main/java/com/litesuits/common/utils/FieldUtil.java @@ -1,128 +1,128 @@ -package com.litesuits.common.utils; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.LinkedList; -import java.util.List; - -/** - * 域工具 - * - * @author mty - * @date 2013-6-10下午6:36:29 - */ -public class FieldUtil { - - /** - * 判断是否序列化 - * - * @param f - * @return - */ - public static boolean isSerializable(Field f) { - Class[] cls = f.getType().getInterfaces(); - for (Class c : cls) { - if (Serializable.class == c) { - return true; - } - } - return false; - } - - /** - * 设置域的值 - * - * @param f - * @param obj - * @return - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - public static Object set(Field f, Object obj, Object value) throws IllegalArgumentException, IllegalAccessException { - f.setAccessible(true); - f.set(obj, value); - return f.get(obj); - } - - /** - * 获取域的值 - * - * @param f - * @param obj - * @return - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - public static Object get(Field f, Object obj) throws IllegalArgumentException, IllegalAccessException { - f.setAccessible(true); - return f.get(obj); - } - - public static boolean isLong(Field field) { - return field.getType() == long.class || field.getType() == Long.class; - } - - public static boolean isInteger(Field field) { - return field.getType() == int.class || field.getType() != Integer.class; - } - - /** - * 获取域的泛型类型,如果不带泛型返回null - * - * @param f - * @return - */ - public static Class getGenericType(Field f) { - Type type = f.getGenericType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getActualTypeArguments()[0]; - if (type instanceof Class) return (Class) type; - } else if (type instanceof Class) return (Class) type; - return null; - } - - /** - * 获取数组的类型 - * - * @param f - * @return - */ - public static Class getComponentType(Field f) { - return f.getType().getComponentType(); - } - - /** - * 获取全部Field,包括父类 - * - * @param claxx - * @return - */ - public static List getAllDeclaredFields(Class claxx) { - // find all field. - LinkedList fieldList = new LinkedList(); - while (claxx != null && claxx != Object.class) { - Field[] fs = claxx.getDeclaredFields(); - for (int i = 0; i < fs.length; i++) { - Field f = fs[i]; - if (!isInvalid(f)) { - fieldList.addLast(f); - } - } - claxx = claxx.getSuperclass(); - } - return fieldList; - } - - /** - * 是静态常量或者内部结构属性 - * - * @param f - * @return - */ - public static boolean isInvalid(Field f) { - return (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) || f.isSynthetic(); - } -} +package com.litesuits.common.utils; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.LinkedList; +import java.util.List; + +/** + * 域工具 + * + * @author mty + * @date 2013-6-10下午6:36:29 + */ +public class FieldUtil { + + /** + * 判断是否序列化 + * + * @param f + * @return + */ + public static boolean isSerializable(Field f) { + Class[] cls = f.getType().getInterfaces(); + for (Class c : cls) { + if (Serializable.class == c) { + return true; + } + } + return false; + } + + /** + * 设置域的值 + * + * @param f + * @param obj + * @return + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + public static Object set(Field f, Object obj, Object value) throws IllegalArgumentException, IllegalAccessException { + f.setAccessible(true); + f.set(obj, value); + return f.get(obj); + } + + /** + * 获取域的值 + * + * @param f + * @param obj + * @return + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + public static Object get(Field f, Object obj) throws IllegalArgumentException, IllegalAccessException { + f.setAccessible(true); + return f.get(obj); + } + + public static boolean isLong(Field field) { + return field.getType() == long.class || field.getType() == Long.class; + } + + public static boolean isInteger(Field field) { + return field.getType() == int.class || field.getType() != Integer.class; + } + + /** + * 获取域的泛型类型,如果不带泛型返回null + * + * @param f + * @return + */ + public static Class getGenericType(Field f) { + Type type = f.getGenericType(); + if (type instanceof ParameterizedType) { + type = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (type instanceof Class) return (Class) type; + } else if (type instanceof Class) return (Class) type; + return null; + } + + /** + * 获取数组的类型 + * + * @param f + * @return + */ + public static Class getComponentType(Field f) { + return f.getType().getComponentType(); + } + + /** + * 获取全部Field,包括父类 + * + * @param claxx + * @return + */ + public static List getAllDeclaredFields(Class claxx) { + // find all field. + LinkedList fieldList = new LinkedList(); + while (claxx != null && claxx != Object.class) { + Field[] fs = claxx.getDeclaredFields(); + for (int i = 0; i < fs.length; i++) { + Field f = fs[i]; + if (!isInvalid(f)) { + fieldList.addLast(f); + } + } + claxx = claxx.getSuperclass(); + } + return fieldList; + } + + /** + * 是静态常量或者内部结构属性 + * + * @param f + * @return + */ + public static boolean isInvalid(Field f) { + return (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) || f.isSynthetic(); + } +} diff --git a/src/com/litesuits/common/utils/FileUtil.java b/app/src/main/java/com/litesuits/common/utils/FileUtil.java similarity index 100% rename from src/com/litesuits/common/utils/FileUtil.java rename to app/src/main/java/com/litesuits/common/utils/FileUtil.java diff --git a/src/com/litesuits/common/utils/HandlerUtil.java b/app/src/main/java/com/litesuits/common/utils/HandlerUtil.java similarity index 100% rename from src/com/litesuits/common/utils/HandlerUtil.java rename to app/src/main/java/com/litesuits/common/utils/HandlerUtil.java diff --git a/src/com/litesuits/common/utils/HexUtil.java b/app/src/main/java/com/litesuits/common/utils/HexUtil.java similarity index 100% rename from src/com/litesuits/common/utils/HexUtil.java rename to app/src/main/java/com/litesuits/common/utils/HexUtil.java diff --git a/src/com/litesuits/common/utils/InputMethodUtils.java b/app/src/main/java/com/litesuits/common/utils/InputMethodUtils.java similarity index 100% rename from src/com/litesuits/common/utils/InputMethodUtils.java rename to app/src/main/java/com/litesuits/common/utils/InputMethodUtils.java diff --git a/src/com/litesuits/common/utils/MD5Util.java b/app/src/main/java/com/litesuits/common/utils/MD5Util.java similarity index 100% rename from src/com/litesuits/common/utils/MD5Util.java rename to app/src/main/java/com/litesuits/common/utils/MD5Util.java diff --git a/src/com/litesuits/common/utils/MemoryUtil.java b/app/src/main/java/com/litesuits/common/utils/MemoryUtil.java similarity index 100% rename from src/com/litesuits/common/utils/MemoryUtil.java rename to app/src/main/java/com/litesuits/common/utils/MemoryUtil.java diff --git a/src/com/litesuits/common/utils/NotificationUtil.java b/app/src/main/java/com/litesuits/common/utils/NotificationUtil.java similarity index 100% rename from src/com/litesuits/common/utils/NotificationUtil.java rename to app/src/main/java/com/litesuits/common/utils/NotificationUtil.java diff --git a/src/com/litesuits/common/utils/NumberUtil.java b/app/src/main/java/com/litesuits/common/utils/NumberUtil.java similarity index 100% rename from src/com/litesuits/common/utils/NumberUtil.java rename to app/src/main/java/com/litesuits/common/utils/NumberUtil.java diff --git a/src/com/litesuits/common/utils/PackageUtil.java b/app/src/main/java/com/litesuits/common/utils/PackageUtil.java similarity index 100% rename from src/com/litesuits/common/utils/PackageUtil.java rename to app/src/main/java/com/litesuits/common/utils/PackageUtil.java diff --git a/src/com/litesuits/common/utils/PollingUtil.java b/app/src/main/java/com/litesuits/common/utils/PollingUtil.java similarity index 100% rename from src/com/litesuits/common/utils/PollingUtil.java rename to app/src/main/java/com/litesuits/common/utils/PollingUtil.java diff --git a/src/com/litesuits/common/utils/RandomUtil.java b/app/src/main/java/com/litesuits/common/utils/RandomUtil.java similarity index 100% rename from src/com/litesuits/common/utils/RandomUtil.java rename to app/src/main/java/com/litesuits/common/utils/RandomUtil.java diff --git a/src/com/litesuits/common/utils/SdCardUtil.java b/app/src/main/java/com/litesuits/common/utils/SdCardUtil.java similarity index 100% rename from src/com/litesuits/common/utils/SdCardUtil.java rename to app/src/main/java/com/litesuits/common/utils/SdCardUtil.java diff --git a/src/com/litesuits/common/utils/ShellUtil.java b/app/src/main/java/com/litesuits/common/utils/ShellUtil.java similarity index 100% rename from src/com/litesuits/common/utils/ShellUtil.java rename to app/src/main/java/com/litesuits/common/utils/ShellUtil.java diff --git a/src/com/litesuits/common/utils/TelephoneUtil.java b/app/src/main/java/com/litesuits/common/utils/TelephoneUtil.java similarity index 100% rename from src/com/litesuits/common/utils/TelephoneUtil.java rename to app/src/main/java/com/litesuits/common/utils/TelephoneUtil.java diff --git a/src/com/litesuits/common/utils/VibrateUtil.java b/app/src/main/java/com/litesuits/common/utils/VibrateUtil.java similarity index 100% rename from src/com/litesuits/common/utils/VibrateUtil.java rename to app/src/main/java/com/litesuits/common/utils/VibrateUtil.java diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..3ea04e7 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,2 @@ + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..c641532 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + android-common + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..73862c4 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..c67c123 --- /dev/null +++ b/build.gradle @@ -0,0 +1,23 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.novoda:bintray-release:0.3.4' + } +} + +allprojects { + repositories { + jcenter() + } + tasks.withType(Javadoc) { + options.addStringOption('Xdoclint:none', '-quiet') + options.addStringOption('encoding', 'UTF-8') + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1d3591c --- /dev/null +++ b/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..13372aef5e24af05341d49695ee84e5f9b594659 GIT binary patch literal 53636 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ⅅ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 H-+%o-(+5aQ literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..122a0dc --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Dec 28 10:00:20 PST 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/jars/lite-common-1.1.1.jar b/jars/lite-common-1.1.1.jar deleted file mode 100644 index bad2b9eba8ccbe96d23f73a62b1bdbdb65566e05..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 147948 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 diff --git a/jars/lite-common-1.1.3.jar b/jars/lite-common-1.1.3.jar deleted file mode 100644 index fd2ed4fcf757a697d45c5fc9ca440e3a2ad491d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148124 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�M@gD)0+tjU*n# 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 Of6mU4q`ml8N%}uJY#h}9 diff --git a/litecommon1.0.0.jar b/litecommon1.0.0.jar deleted file mode 100644 index 40df121ea7fdde6c433a55f58201526b418ec95b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 70127 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 - - LiteCommon - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..e7b4def --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':app' From b789b98f72db5e5366c29fc6e4f7e20bf09b56dd Mon Sep 17 00:00:00 2001 From: luffykou Date: Wed, 3 Aug 2016 16:41:40 +0800 Subject: [PATCH 2/4] change project to android studio type --- android-common.iml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/android-common.iml b/android-common.iml index 2fc997b..7727955 100644 --- a/android-common.iml +++ b/android-common.iml @@ -1,17 +1,17 @@ - + - + - - + - - + From 054ca177fc559703ef989ac8ce41d48d3fb230ff Mon Sep 17 00:00:00 2001 From: luffykou Date: Wed, 3 Aug 2016 16:46:23 +0800 Subject: [PATCH 3/4] change project to android studio type --- .../com/litesuits/common/assist/Averager.java | 140 +- .../com/litesuits/common/assist/Check.java | 66 +- .../litesuits/common/assist/TimeAverager.java | 130 +- .../litesuits/common/assist/TimeCounter.java | 114 +- .../com/litesuits/common/io/Charsets.java | 74 +- .../com/litesuits/common/io/FileUtils.java | 5094 ++++++++--------- .../litesuits/common/io/FilenameUtils.java | 2126 +++---- .../java/com/litesuits/common/io/IOUtils.java | 4818 ++++++++-------- .../io/stream/ByteArrayOutputStream.java | 714 +-- .../common/io/stream/ClosedInputStream.java | 98 +- .../common/io/stream/StringBuilderWriter.java | 320 +- .../com/litesuits/common/utils/ClassUtil.java | 142 +- .../com/litesuits/common/utils/FieldUtil.java | 256 +- gradlew.bat | 180 +- 14 files changed, 7136 insertions(+), 7136 deletions(-) diff --git a/app/src/main/java/com/litesuits/common/assist/Averager.java b/app/src/main/java/com/litesuits/common/assist/Averager.java index 8a7c93f..131e986 100755 --- a/app/src/main/java/com/litesuits/common/assist/Averager.java +++ b/app/src/main/java/com/litesuits/common/assist/Averager.java @@ -1,70 +1,70 @@ -package com.litesuits.common.assist; - -import com.litesuits.android.log.Log; - -import java.util.ArrayList; - -/** - * 用以统计平均数 - * - * @author MaTianyu - * 2013-12-11下午3:31:03 - */ -public class Averager { - private static final String TAG = "Averager"; - private ArrayList numList = new ArrayList(); - - /** - * 添加一个数字 - * - * @param num - */ - public synchronized void add(Number num) { - numList.add(num); - } - - /** - * 清除全部 - */ - public void clear() { - numList.clear(); - } - - /** - * 返回参与均值计算的数字个数 - * - * @return - */ - public Number size() { - return numList.size(); - } - - /** - * 获取平均数 - * - * @return - */ - public Number getAverage() { - if (numList.size() == 0) { - return 0; - } else { - Float sum = 0f; - for (int i = 0, size = numList.size(); i < size; i++) { - sum = sum.floatValue() + numList.get(i).floatValue(); - } - return sum / numList.size(); - } - } - - /** - * 打印数字列 - * - * @return - */ - public String print() { - String str = "PrintList(" + size() + "): " + numList; - Log.i(TAG, str); - return str; - } - -} +package com.litesuits.common.assist; + +import com.litesuits.android.log.Log; + +import java.util.ArrayList; + +/** + * 用以统计平均数 + * + * @author MaTianyu + * 2013-12-11下午3:31:03 + */ +public class Averager { + private static final String TAG = "Averager"; + private ArrayList numList = new ArrayList(); + + /** + * 添加一个数字 + * + * @param num + */ + public synchronized void add(Number num) { + numList.add(num); + } + + /** + * 清除全部 + */ + public void clear() { + numList.clear(); + } + + /** + * 返回参与均值计算的数字个数 + * + * @return + */ + public Number size() { + return numList.size(); + } + + /** + * 获取平均数 + * + * @return + */ + public Number getAverage() { + if (numList.size() == 0) { + return 0; + } else { + Float sum = 0f; + for (int i = 0, size = numList.size(); i < size; i++) { + sum = sum.floatValue() + numList.get(i).floatValue(); + } + return sum / numList.size(); + } + } + + /** + * 打印数字列 + * + * @return + */ + public String print() { + String str = "PrintList(" + size() + "): " + numList; + Log.i(TAG, str); + return str; + } + +} diff --git a/app/src/main/java/com/litesuits/common/assist/Check.java b/app/src/main/java/com/litesuits/common/assist/Check.java index 16c688c..f6ecb87 100755 --- a/app/src/main/java/com/litesuits/common/assist/Check.java +++ b/app/src/main/java/com/litesuits/common/assist/Check.java @@ -1,33 +1,33 @@ -package com.litesuits.common.assist; - -import java.util.Collection; -import java.util.Map; - -/** - * 辅助判断 - * - * @author mty - * @date 2013-6-10下午5:50:57 - */ -public class Check { - - public static boolean isEmpty(CharSequence str) { - return isNull(str) || str.length() == 0; - } - - public static boolean isEmpty(Object[] os) { - return isNull(os) || os.length == 0; - } - - public static boolean isEmpty(Collection l) { - return isNull(l) || l.isEmpty(); - } - - public static boolean isEmpty(Map m) { - return isNull(m) || m.isEmpty(); - } - - public static boolean isNull(Object o) { - return o == null; - } -} +package com.litesuits.common.assist; + +import java.util.Collection; +import java.util.Map; + +/** + * 辅助判断 + * + * @author mty + * @date 2013-6-10下午5:50:57 + */ +public class Check { + + public static boolean isEmpty(CharSequence str) { + return isNull(str) || str.length() == 0; + } + + public static boolean isEmpty(Object[] os) { + return isNull(os) || os.length == 0; + } + + public static boolean isEmpty(Collection l) { + return isNull(l) || l.isEmpty(); + } + + public static boolean isEmpty(Map m) { + return isNull(m) || m.isEmpty(); + } + + public static boolean isNull(Object o) { + return o == null; + } +} diff --git a/app/src/main/java/com/litesuits/common/assist/TimeAverager.java b/app/src/main/java/com/litesuits/common/assist/TimeAverager.java index f5708e5..f2d50b2 100755 --- a/app/src/main/java/com/litesuits/common/assist/TimeAverager.java +++ b/app/src/main/java/com/litesuits/common/assist/TimeAverager.java @@ -1,65 +1,65 @@ -package com.litesuits.common.assist; - - -/** - * 时间均值计算器,只能用于单线程计时。 - * - * @author MaTianyu - * 2013-12-12上午1:23:19 - */ -public class TimeAverager { - /** - * 计时器 - */ - private TimeCounter tc = new TimeCounter(); - /** - * 均值器 - */ - private Averager av = new Averager(); - - /** - * 一个计时开始 - */ - public long start() { - return tc.start(); - } - - /** - * 一个计时结束 - */ - public long end() { - long time = tc.duration(); - av.add(time); - return time; - } - - /** - * 一个计时结束,并且启动下次计时。 - */ - public long endAndRestart() { - long time = tc.durationRestart(); - av.add(time); - return time; - } - - /** - * 求全部计时均值 - */ - public Number average() { - return av.getAverage(); - } - - /** - * 打印全部时间值 - */ - public void print() { - av.print(); - } - - /** - * 清楚数据 - */ - public void clear() { - av.clear(); - } -} +package com.litesuits.common.assist; + + +/** + * 时间均值计算器,只能用于单线程计时。 + * + * @author MaTianyu + * 2013-12-12上午1:23:19 + */ +public class TimeAverager { + /** + * 计时器 + */ + private TimeCounter tc = new TimeCounter(); + /** + * 均值器 + */ + private Averager av = new Averager(); + + /** + * 一个计时开始 + */ + public long start() { + return tc.start(); + } + + /** + * 一个计时结束 + */ + public long end() { + long time = tc.duration(); + av.add(time); + return time; + } + + /** + * 一个计时结束,并且启动下次计时。 + */ + public long endAndRestart() { + long time = tc.durationRestart(); + av.add(time); + return time; + } + + /** + * 求全部计时均值 + */ + public Number average() { + return av.getAverage(); + } + + /** + * 打印全部时间值 + */ + public void print() { + av.print(); + } + + /** + * 清楚数据 + */ + public void clear() { + av.clear(); + } +} diff --git a/app/src/main/java/com/litesuits/common/assist/TimeCounter.java b/app/src/main/java/com/litesuits/common/assist/TimeCounter.java index dcd6d51..a93e274 100755 --- a/app/src/main/java/com/litesuits/common/assist/TimeCounter.java +++ b/app/src/main/java/com/litesuits/common/assist/TimeCounter.java @@ -1,58 +1,58 @@ -package com.litesuits.common.assist; - -import com.litesuits.android.log.Log; - -/** - * Time Counter. - * - * @author MaTianyu - * 2013-12-11下午3:42:28 - */ -public class TimeCounter { - - private static final String TAG = TimeCounter.class.getSimpleName(); - private long t; - - public TimeCounter() { - start(); - } - - /** - * Count start. - */ - public long start() { - t = System.currentTimeMillis(); - return t; - } - - /** - * Get duration and restart. - */ - public long durationRestart() { - long now = System.currentTimeMillis(); - long d = now - t; - t = now; - return d; - } - - /** - * Get duration. - */ - public long duration() { - return System.currentTimeMillis() - t; - } - - /** - * Print duration. - */ - public void printDuration(String tag) { - Log.i(TAG, tag + " : " + duration()); - } - - /** - * Print duration. - */ - public void printDurationRestart(String tag) { - Log.i(TAG, tag + " : " + durationRestart()); - } +package com.litesuits.common.assist; + +import com.litesuits.android.log.Log; + +/** + * Time Counter. + * + * @author MaTianyu + * 2013-12-11下午3:42:28 + */ +public class TimeCounter { + + private static final String TAG = TimeCounter.class.getSimpleName(); + private long t; + + public TimeCounter() { + start(); + } + + /** + * Count start. + */ + public long start() { + t = System.currentTimeMillis(); + return t; + } + + /** + * Get duration and restart. + */ + public long durationRestart() { + long now = System.currentTimeMillis(); + long d = now - t; + t = now; + return d; + } + + /** + * Get duration. + */ + public long duration() { + return System.currentTimeMillis() - t; + } + + /** + * Print duration. + */ + public void printDuration(String tag) { + Log.i(TAG, tag + " : " + duration()); + } + + /** + * Print duration. + */ + public void printDurationRestart(String tag) { + Log.i(TAG, tag + " : " + durationRestart()); + } } \ No newline at end of file diff --git a/app/src/main/java/com/litesuits/common/io/Charsets.java b/app/src/main/java/com/litesuits/common/io/Charsets.java index 044df22..17dfd97 100644 --- a/app/src/main/java/com/litesuits/common/io/Charsets.java +++ b/app/src/main/java/com/litesuits/common/io/Charsets.java @@ -1,37 +1,37 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import java.nio.charset.Charset; - -/** - * Charsets - */ -public class Charsets { - public static Charset toCharset(Charset charset) { - return charset == null ? Charset.defaultCharset() : charset; - } - public static Charset toCharset(String charset) { - return charset == null ? Charset.defaultCharset() : Charset.forName(charset); - } - public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); - public static final Charset US_ASCII = Charset.forName("US-ASCII"); - public static final Charset UTF_16 = Charset.forName("UTF-16"); - public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); - public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); - public static final Charset UTF_8 = Charset.forName("UTF-8"); -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import java.nio.charset.Charset; + +/** + * Charsets + */ +public class Charsets { + public static Charset toCharset(Charset charset) { + return charset == null ? Charset.defaultCharset() : charset; + } + public static Charset toCharset(String charset) { + return charset == null ? Charset.defaultCharset() : Charset.forName(charset); + } + public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + public static final Charset US_ASCII = Charset.forName("US-ASCII"); + public static final Charset UTF_16 = Charset.forName("UTF-16"); + public static final Charset UTF_16BE = Charset.forName("UTF-16BE"); + public static final Charset UTF_16LE = Charset.forName("UTF-16LE"); + public static final Charset UTF_8 = Charset.forName("UTF-8"); +} diff --git a/app/src/main/java/com/litesuits/common/io/FileUtils.java b/app/src/main/java/com/litesuits/common/io/FileUtils.java index 87ad6d3..c9a02ae 100644 --- a/app/src/main/java/com/litesuits/common/io/FileUtils.java +++ b/app/src/main/java/com/litesuits/common/io/FileUtils.java @@ -1,2547 +1,2547 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import java.io.*; -import java.math.BigInteger; -import java.net.URL; -import java.net.URLConnection; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.charset.Charset; -import java.util.*; - - -/** - * General file manipulation utilities. - *

    - * Facilities are provided in the following areas: - *

      - *
    • writing to a file - *
    • reading from a file - *
    • make a directory including parent directories - *
    • copying files and directories - *
    • deleting files and directories - *
    • converting to and from a URL - *
    • listing files and directories by filter and extension - *
    • comparing file content - *
    • file last changed date - *
    • calculating a checksum - *
    - *

    - * Origin of code: Excalibur, Alexandria, Commons-Utils - * - * @version $Id: FileUtils.java 1349509 2012-06-12 20:39:23Z ggregory $ - */ -public class FileUtils { - - /** - * Instances should NOT be constructed in standard programming. - */ - public FileUtils() { - super(); - } - - /** - * The number of bytes in a kilobyte. - */ - public static final long ONE_KB = 1024; - - /** - * The number of bytes in a kilobyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB); - - /** - * The number of bytes in a megabyte. - */ - public static final long ONE_MB = ONE_KB * ONE_KB; - - /** - * The number of bytes in a megabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI); - - /** - * The file copy buffer size (30 MB) - */ - private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; - - /** - * The number of bytes in a gigabyte. - */ - public static final long ONE_GB = ONE_KB * ONE_MB; - - /** - * The number of bytes in a gigabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI); - - /** - * The number of bytes in a terabyte. - */ - public static final long ONE_TB = ONE_KB * ONE_GB; - - /** - * The number of bytes in a terabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI); - - /** - * The number of bytes in a petabyte. - */ - public static final long ONE_PB = ONE_KB * ONE_TB; - - /** - * The number of bytes in a petabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI); - - /** - * The number of bytes in an exabyte. - */ - public static final long ONE_EB = ONE_KB * ONE_PB; - - /** - * The number of bytes in an exabyte. - * - * @since 2.4 - */ - public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI); - - /** - * The number of bytes in a zettabyte. - */ - public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB)); - - /** - * The number of bytes in a yottabyte. - */ - public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB); - - /** - * An empty array of type File. - */ - public static final File[] EMPTY_FILE_ARRAY = new File[0]; - - /** - * The UTF-8 character set, used to decode octets in URLs. - */ - private static final Charset UTF8 = Charset.forName("UTF-8"); - - //----------------------------------------------------------------------- - - /** - * Construct a file from the set of name elements. - * - * @param directory the parent directory - * @param names the name elements - * @return the file - * @since 2.1 - */ - public static File getFile(File directory, String... names) { - if (directory == null) { - throw new NullPointerException("directorydirectory must not be null"); - } - if (names == null) { - throw new NullPointerException("names must not be null"); - } - File file = directory; - for (String name : names) { - file = new File(file, name); - } - return file; - } - - /** - * Construct a file from the set of name elements. - * - * @param names the name elements - * @return the file - * @since 2.1 - */ - public static File getFile(String... names) { - if (names == null) { - throw new NullPointerException("names must not be null"); - } - File file = null; - for (String name : names) { - if (file == null) { - file = new File(name); - } else { - file = new File(file, name); - } - } - return file; - } - - /** - * Returns the path to the system temporary directory. - * - * @return the path to the system temporary directory. - * @since 2.0 - */ - public static String getTempDirectoryPath() { - return System.getProperty("java.io.tmpdir"); - } - - /** - * Returns a {@link java.io.File} representing the system temporary directory. - * - * @return the system temporary directory. - * @since 2.0 - */ - public static File getTempDirectory() { - return new File(getTempDirectoryPath()); - } - - /** - * Returns the path to the user's home directory. - * - * @return the path to the user's home directory. - * @since 2.0 - */ - public static String getUserDirectoryPath() { - return System.getProperty("user.home"); - } - - /** - * Returns a {@link java.io.File} representing the user's home directory. - * - * @return the user's home directory. - * @since 2.0 - */ - public static File getUserDirectory() { - return new File(getUserDirectoryPath()); - } - - //----------------------------------------------------------------------- - - /** - * Opens a {@link java.io.FileInputStream} for the specified file, providing better - * error messages than simply calling new FileInputStream(file). - *

    - * At the end of the method either the stream will be successfully opened, - * or an exception will have been thrown. - *

    - * An exception is thrown if the file does not exist. - * An exception is thrown if the file object exists but is a directory. - * An exception is thrown if the file exists but cannot be read. - * - * @param file the file to open for input, must not be {@code null} - * @return a new {@link java.io.FileInputStream} for the specified file - * @throws java.io.FileNotFoundException if the file does not exist - * @throws java.io.IOException if the file object is a directory - * @throws java.io.IOException if the file cannot be read - * @since 1.3 - */ - public static FileInputStream openInputStream(File file) throws IOException { - if (file.exists()) { - if (file.isDirectory()) { - throw new IOException("File '" + file + "' exists but is a directory"); - } - if (file.canRead() == false) { - throw new IOException("File '" + file + "' cannot be read"); - } - } else { - throw new FileNotFoundException("File '" + file + "' does not exist"); - } - return new FileInputStream(file); - } - - //----------------------------------------------------------------------- - - /** - * Opens a {@link java.io.FileOutputStream} for the specified file, checking and - * creating the parent directory if it does not exist. - *

    - * At the end of the method either the stream will be successfully opened, - * or an exception will have been thrown. - *

    - * The parent directory will be created if it does not exist. - * The file will be created if it does not exist. - * An exception is thrown if the file object exists but is a directory. - * An exception is thrown if the file exists but cannot be written to. - * An exception is thrown if the parent directory cannot be created. - * - * @param file the file to open for output, must not be {@code null} - * @return a new {@link java.io.FileOutputStream} for the specified file - * @throws java.io.IOException if the file object is a directory - * @throws java.io.IOException if the file cannot be written to - * @throws java.io.IOException if a parent directory needs creating but that fails - * @since 1.3 - */ - public static FileOutputStream openOutputStream(File file) throws IOException { - return openOutputStream(file, false); - } - - /** - * Opens a {@link java.io.FileOutputStream} for the specified file, checking and - * creating the parent directory if it does not exist. - *

    - * At the end of the method either the stream will be successfully opened, - * or an exception will have been thrown. - *

    - * The parent directory will be created if it does not exist. - * The file will be created if it does not exist. - * An exception is thrown if the file object exists but is a directory. - * An exception is thrown if the file exists but cannot be written to. - * An exception is thrown if the parent directory cannot be created. - * - * @param file the file to open for output, must not be {@code null} - * @param append if {@code true}, then bytes will be added to the - * end of the file rather than overwriting - * @return a new {@link java.io.FileOutputStream} for the specified file - * @throws java.io.IOException if the file object is a directory - * @throws java.io.IOException if the file cannot be written to - * @throws java.io.IOException if a parent directory needs creating but that fails - * @since 2.1 - */ - public static FileOutputStream openOutputStream(File file, boolean append) throws IOException { - if (file.exists()) { - if (file.isDirectory()) { - throw new IOException("File '" + file + "' exists but is a directory"); - } - if (file.canWrite() == false) { - throw new IOException("File '" + file + "' cannot be written to"); - } - } else { - File parent = file.getParentFile(); - if (parent != null) { - if (!parent.mkdirs() && !parent.isDirectory()) { - throw new IOException("Directory '" + parent + "' could not be created"); - } - } - } - return new FileOutputStream(file, append); - } - - //----------------------------------------------------------------------- - - /** - * Returns a human-readable version of the file size, where the input represents a specific number of bytes. - *

    - * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the - * nearest GB boundary. - *

    - *

    - * Similarly for the 1MB and 1KB boundaries. - *

    - * - * @param size the number of bytes - * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) - * @see
    IO-226 - should the rounding be changed? - * @since 2.4 - */ - // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? - public static String byteCountToDisplaySize(BigInteger size) { - String displaySize; - - if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_EB_BI)) + " EB"; - } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_PB_BI)) + " PB"; - } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_TB_BI)) + " TB"; - } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_GB_BI)) + " GB"; - } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_MB_BI)) + " MB"; - } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) { - displaySize = String.valueOf(size.divide(ONE_KB_BI)) + " KB"; - } else { - displaySize = String.valueOf(size) + " bytes"; - } - return displaySize; - } - - /** - * Returns a human-readable version of the file size, where the input represents a specific number of bytes. - *

    - * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the - * nearest GB boundary. - *

    - *

    - * Similarly for the 1MB and 1KB boundaries. - *

    - * - * @param size the number of bytes - * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) - * @see IO-226 - should the rounding be changed? - */ - // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? - public static String byteCountToDisplaySize(long size) { - return byteCountToDisplaySize(BigInteger.valueOf(size)); - } - - //----------------------------------------------------------------------- - - /** - * Implements the same behaviour as the "touch" utility on Unix. It creates - * a new file with size 0 or, if the file exists already, it is opened and - * closed without modifying it, but updating the file date and time. - *

    - * NOTE: As from v1.3, this method throws an IOException if the last - * modified date of the file cannot be set. Also, as from v1.3 this method - * creates parent directories if they do not exist. - * - * @param file the File to touch - * @throws java.io.IOException If an I/O problem occurs - */ - public static void touch(File file) throws IOException { - if (!file.exists()) { - OutputStream out = openOutputStream(file); - IOUtils.closeQuietly(out); - } - boolean success = file.setLastModified(System.currentTimeMillis()); - if (!success) { - throw new IOException("Unable to set the last modification time for " + file); - } - } - - //----------------------------------------------------------------------- - - /** - * Converts a Collection containing java.io.File instanced into array - * representation. This is to account for the difference between - * File.listFiles() and FileUtils.listFiles(). - * - * @param files a Collection containing java.io.File instances - * @return an array of java.io.File - */ - public static File[] convertFileCollectionToFileArray(Collection files) { - return files.toArray(new File[files.size()]); - } - - //----------------------------------------------------------------------- - - /** - * Converts an array of file extensions to suffixes for use - * with IOFileFilters. - * - * @param extensions an array of extensions. Format: {"java", "xml"} - * @return an array of suffixes. Format: {".java", ".xml"} - */ - private static String[] toSuffixes(String[] extensions) { - String[] suffixes = new String[extensions.length]; - for (int i = 0; i < extensions.length; i++) { - suffixes[i] = "." + extensions[i]; - } - return suffixes; - } - - //----------------------------------------------------------------------- - - /** - * Compares the contents of two files to determine if they are equal or not. - *

    - * This method checks to see if the two files are different lengths - * or if they point to the same file, before resorting to byte-by-byte - * comparison of the contents. - *

    - * Code origin: Avalon - * - * @param file1 the first file - * @param file2 the second file - * @return true if the content of the files are equal or they both don't - * exist, false otherwise - * @throws java.io.IOException in case of an I/O error - */ - public static boolean contentEquals(File file1, File file2) throws IOException { - boolean file1Exists = file1.exists(); - if (file1Exists != file2.exists()) { - return false; - } - - if (!file1Exists) { - // two not existing files are equal - return true; - } - - if (file1.isDirectory() || file2.isDirectory()) { - // don't want to compare directory contents - throw new IOException("Can't compare directories, only files"); - } - - if (file1.length() != file2.length()) { - // lengths differ, cannot be equal - return false; - } - - if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { - // same file - return true; - } - - InputStream input1 = null; - InputStream input2 = null; - try { - input1 = new FileInputStream(file1); - input2 = new FileInputStream(file2); - return IOUtils.contentEquals(input1, input2); - - } finally { - IOUtils.closeQuietly(input1); - IOUtils.closeQuietly(input2); - } - } - - //----------------------------------------------------------------------- - - /** - * Compares the contents of two files to determine if they are equal or not. - *

    - * This method checks to see if the two files point to the same file, - * before resorting to line-by-line comparison of the contents. - *

    - * - * @param file1 the first file - * @param file2 the second file - * @param charsetName the character encoding to be used. - * May be null, in which case the platform default is used - * @return true if the content of the files are equal or neither exists, - * false otherwise - * @throws java.io.IOException in case of an I/O error - * @see IOUtils#contentEqualsIgnoreEOL(java.io.Reader, java.io.Reader) - * @since 2.2 - */ - public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException { - boolean file1Exists = file1.exists(); - if (file1Exists != file2.exists()) { - return false; - } - - if (!file1Exists) { - // two not existing files are equal - return true; - } - - if (file1.isDirectory() || file2.isDirectory()) { - // don't want to compare directory contents - throw new IOException("Can't compare directories, only files"); - } - - if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { - // same file - return true; - } - - Reader input1 = null; - Reader input2 = null; - try { - if (charsetName == null) { - input1 = new InputStreamReader(new FileInputStream(file1)); - input2 = new InputStreamReader(new FileInputStream(file2)); - } else { - input1 = new InputStreamReader(new FileInputStream(file1), charsetName); - input2 = new InputStreamReader(new FileInputStream(file2), charsetName); - } - return IOUtils.contentEqualsIgnoreEOL(input1, input2); - - } finally { - IOUtils.closeQuietly(input1); - IOUtils.closeQuietly(input2); - } - } - - //----------------------------------------------------------------------- - - /** - * Convert from a URL to a File. - *

    - * From version 1.1 this method will decode the URL. - * Syntax such as file:///my%20docs/file.txt will be - * correctly decoded to /my docs/file.txt. Starting with version - * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters. - * Additionally, malformed percent-encoded octets are handled leniently by - * passing them through literally. - * - * @param url the file URL to convert, {@code null} returns {@code null} - * @return the equivalent File object, or {@code null} - * if the URL's protocol is not file - */ - public static File toFile(URL url) { - if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) { - return null; - } else { - String filename = url.getFile().replace('/', File.separatorChar); - filename = decodeUrl(filename); - return new File(filename); - } - } - - /** - * Decodes the specified URL as per RFC 3986, i.e. transforms - * percent-encoded octets to characters by decoding with the UTF-8 character - * set. This function is primarily intended for usage with - * {@link java.net.URL} which unfortunately does not enforce proper URLs. As - * such, this method will leniently accept invalid characters or malformed - * percent-encoded octets and simply pass them literally through to the - * result string. Except for rare edge cases, this will make unencoded URLs - * pass through unaltered. - * - * @param url The URL to decode, may be {@code null}. - * @return The decoded URL or {@code null} if the input was - * {@code null}. - */ - static String decodeUrl(String url) { - String decoded = url; - if (url != null && url.indexOf('%') >= 0) { - int n = url.length(); - StringBuffer buffer = new StringBuffer(); - ByteBuffer bytes = ByteBuffer.allocate(n); - for (int i = 0; i < n; ) { - if (url.charAt(i) == '%') { - try { - do { - byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); - bytes.put(octet); - i += 3; - } while (i < n && url.charAt(i) == '%'); - continue; - } catch (RuntimeException e) { - // malformed percent-encoded octet, fall through and - // append characters literally - } finally { - if (bytes.position() > 0) { - bytes.flip(); - buffer.append(UTF8.decode(bytes).toString()); - bytes.clear(); - } - } - } - buffer.append(url.charAt(i++)); - } - decoded = buffer.toString(); - } - return decoded; - } - - /** - * Converts each of an array of URL to a File. - *

    - * Returns an array of the same size as the input. - * If the input is {@code null}, an empty array is returned. - * If the input contains {@code null}, the output array contains {@code null} at the same - * index. - *

    - * This method will decode the URL. - * Syntax such as file:///my%20docs/file.txt will be - * correctly decoded to /my docs/file.txt. - * - * @param urls the file URLs to convert, {@code null} returns empty array - * @return a non-{@code null} array of Files matching the input, with a {@code null} item - * if there was a {@code null} at that index in the input array - * @throws IllegalArgumentException if any file is not a URL file - * @throws IllegalArgumentException if any file is incorrectly encoded - * @since 1.1 - */ - public static File[] toFiles(URL[] urls) { - if (urls == null || urls.length == 0) { - return EMPTY_FILE_ARRAY; - } - File[] files = new File[urls.length]; - for (int i = 0; i < urls.length; i++) { - URL url = urls[i]; - if (url != null) { - if (url.getProtocol().equals("file") == false) { - throw new IllegalArgumentException( - "URL could not be converted to a File: " + url); - } - files[i] = toFile(url); - } - } - return files; - } - - /** - * Converts each of an array of File to a URL. - *

    - * Returns an array of the same size as the input. - * - * @param files the files to convert, must not be {@code null} - * @return an array of URLs matching the input - * @throws java.io.IOException if a file cannot be converted - * @throws NullPointerException if the parameter is null - */ - public static URL[] toURLs(File[] files) throws IOException { - URL[] urls = new URL[files.length]; - - for (int i = 0; i < urls.length; i++) { - urls[i] = files[i].toURI().toURL(); - } - - return urls; - } - - //----------------------------------------------------------------------- - - /** - * Copies a file to a directory preserving the file date. - *

    - * This method copies the contents of the specified source file - * to a file of the same name in the specified destination directory. - * The destination directory is created if it does not exist. - * If the destination file exists, then this method will overwrite it. - *

    - * Note: This method tries to preserve the file's last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destDir the directory to place the copy in, must not be {@code null} - * @throws NullPointerException if source or destination is null - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFile(java.io.File, java.io.File, boolean) - */ - public static void copyFileToDirectory(File srcFile, File destDir) throws IOException { - copyFileToDirectory(srcFile, destDir, true); - } - - /** - * Copies a file to a directory optionally preserving the file date. - *

    - * This method copies the contents of the specified source file - * to a file of the same name in the specified destination directory. - * The destination directory is created if it does not exist. - * If the destination file exists, then this method will overwrite it. - *

    - * Note: Setting preserveFileDate to - * {@code true} tries to preserve the file's last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destDir the directory to place the copy in, must not be {@code null} - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFile(java.io.File, java.io.File, boolean) - * @since 1.3 - */ - public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException { - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (destDir.exists() && destDir.isDirectory() == false) { - throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); - } - File destFile = new File(destDir, srcFile.getName()); - copyFile(srcFile, destFile, preserveFileDate); - } - - /** - * Copies a file to a new location preserving the file date. - *

    - * This method copies the contents of the specified source file to the - * specified destination file. The directory holding the destination file is - * created if it does not exist. If the destination file exists, then this - * method will overwrite it. - *

    - * Note: This method tries to preserve the file's last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destFile the new file, must not be {@code null} - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFileToDirectory(java.io.File, java.io.File) - */ - public static void copyFile(File srcFile, File destFile) throws IOException { - copyFile(srcFile, destFile, true); - } - - /** - * Copies a file to a new location. - *

    - * This method copies the contents of the specified source file - * to the specified destination file. - * The directory holding the destination file is created if it does not exist. - * If the destination file exists, then this method will overwrite it. - *

    - * Note: Setting preserveFileDate to - * {@code true} tries to preserve the file's last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that the operation will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcFile an existing file to copy, must not be {@code null} - * @param destFile the new file, must not be {@code null} - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @see #copyFileToDirectory(java.io.File, java.io.File, boolean) - */ - public static void copyFile(File srcFile, File destFile, - boolean preserveFileDate) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destFile == null) { - throw new NullPointerException("Destination must not be null"); - } - if (srcFile.exists() == false) { - throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); - } - if (srcFile.isDirectory()) { - throw new IOException("Source '" + srcFile + "' exists but is a directory"); - } - if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { - throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); - } - File parentFile = destFile.getParentFile(); - if (parentFile != null) { - if (!parentFile.mkdirs() && !parentFile.isDirectory()) { - throw new IOException("Destination '" + parentFile + "' directory cannot be created"); - } - } - if (destFile.exists() && destFile.canWrite() == false) { - throw new IOException("Destination '" + destFile + "' exists but is read-only"); - } - doCopyFile(srcFile, destFile, preserveFileDate); - } - - /** - * Copy bytes from a File to an OutputStream. - *

    - * This method buffers the input internally, so there is no need to use a BufferedInputStream. - *

    - * - * @param input the File to read from - * @param output the OutputStream to write to - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.1 - */ - public static long copyFile(File input, OutputStream output) throws IOException { - final FileInputStream fis = new FileInputStream(input); - try { - return IOUtils.copyLarge(fis, output); - } finally { - fis.close(); - } - } - - /** - * Internal copy file method. - * - * @param srcFile the validated source file, must not be {@code null} - * @param destFile the validated destination file, must not be {@code null} - * @param preserveFileDate whether to preserve the file date - * @throws java.io.IOException if an error occurs - */ - private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { - if (destFile.exists() && destFile.isDirectory()) { - throw new IOException("Destination '" + destFile + "' exists but is a directory"); - } - - FileInputStream fis = null; - FileOutputStream fos = null; - FileChannel input = null; - FileChannel output = null; - try { - fis = new FileInputStream(srcFile); - fos = new FileOutputStream(destFile); - input = fis.getChannel(); - output = fos.getChannel(); - long size = input.size(); - long pos = 0; - long count = 0; - while (pos < size) { - count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos; - pos += output.transferFrom(input, pos, count); - } - } finally { - IOUtils.closeQuietly(output); - IOUtils.closeQuietly(fos); - IOUtils.closeQuietly(input); - IOUtils.closeQuietly(fis); - } - - if (srcFile.length() != destFile.length()) { - throw new IOException("Failed to copy full contents from '" + - srcFile + "' to '" + destFile + "'"); - } - if (preserveFileDate) { - destFile.setLastModified(srcFile.lastModified()); - } - } - - //----------------------------------------------------------------------- - - /** - * Copies a directory to within another directory preserving the file dates. - *

    - * This method copies the source directory and all its contents to a - * directory of the same name in the specified destination directory. - *

    - * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

    - * Note: This method tries to preserve the files' last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the directory to place the copy in, must not be {@code null} - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.2 - */ - public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException { - if (srcDir == null) { - throw new NullPointerException("Source must not be null"); - } - if (srcDir.exists() && srcDir.isDirectory() == false) { - throw new IllegalArgumentException("Source '" + destDir + "' is not a directory"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (destDir.exists() && destDir.isDirectory() == false) { - throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); - } - copyDirectory(srcDir, new File(destDir, srcDir.getName()), true); - } - - /** - * Copies a whole directory to a new location preserving the file dates. - *

    - * This method copies the specified directory and all its child - * directories and files to the specified destination. - * The destination is the new location and name of the directory. - *

    - * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

    - * Note: This method tries to preserve the files' last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.1 - */ - public static void copyDirectory(File srcDir, File destDir) throws IOException { - copyDirectory(srcDir, destDir, true); - } - - /** - * Copies a whole directory to a new location. - *

    - * This method copies the contents of the specified source directory - * to within the specified destination directory. - *

    - * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

    - * Note: Setting preserveFileDate to - * {@code true} tries to preserve the files' last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.1 - */ - public static void copyDirectory(File srcDir, File destDir, - boolean preserveFileDate) throws IOException { - copyDirectory(srcDir, destDir, null, preserveFileDate); - } - - /** - * Copies a filtered directory to a new location preserving the file dates. - *

    - * This method copies the contents of the specified source directory - * to within the specified destination directory. - *

    - * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

    - * Note: This method tries to preserve the files' last - * modified date/times using {@link java.io.File#setLastModified(long)}, however - * it is not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - *

    - *

    Example: Copy directories only

    - *
    -     *  // only copy the directory structure
    -     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
    -     *  
    - * - *

    Example: Copy directories and txt files

    - *
    -     *  // Create a filter for ".txt" files
    -     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
    -     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
    -     *
    -     *  // Create a filter for either directories or ".txt" files
    -     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
    -     *
    -     *  // Copy using the filter
    -     *  FileUtils.copyDirectory(srcDir, destDir, filter);
    -     *  
    - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @param filter the filter to apply, null means copy all directories and files - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.4 - */ - public static void copyDirectory(File srcDir, File destDir, - FileFilter filter) throws IOException { - copyDirectory(srcDir, destDir, filter, true); - } - - /** - * Copies a filtered directory to a new location. - *

    - * This method copies the contents of the specified source directory - * to within the specified destination directory. - *

    - * The destination directory is created if it does not exist. - * If the destination directory did exist, then this method merges - * the source with the destination, with the source taking precedence. - *

    - * Note: Setting preserveFileDate to - * {@code true} tries to preserve the files' last modified - * date/times using {@link java.io.File#setLastModified(long)}, however it is - * not guaranteed that those operations will succeed. - * If the modification operation fails, no indication is provided. - *

    - *

    Example: Copy directories only

    - *
    -     *  // only copy the directory structure
    -     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
    -     *  
    - * - *

    Example: Copy directories and txt files

    - *
    -     *  // Create a filter for ".txt" files
    -     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
    -     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
    -     *
    -     *  // Create a filter for either directories or ".txt" files
    -     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
    -     *
    -     *  // Copy using the filter
    -     *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
    -     *  
    - * - * @param srcDir an existing directory to copy, must not be {@code null} - * @param destDir the new directory, must not be {@code null} - * @param filter the filter to apply, null means copy all directories and files - * @param preserveFileDate true if the file date of the copy - * should be the same as the original - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs during copying - * @since 1.4 - */ - public static void copyDirectory(File srcDir, File destDir, - FileFilter filter, boolean preserveFileDate) throws IOException { - if (srcDir == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (srcDir.exists() == false) { - throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); - } - if (srcDir.isDirectory() == false) { - throw new IOException("Source '" + srcDir + "' exists but is not a directory"); - } - if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) { - throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); - } - - // Cater for destination being directory within the source directory (see IO-141) - List exclusionList = null; - if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { - File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); - if (srcFiles != null && srcFiles.length > 0) { - exclusionList = new ArrayList(srcFiles.length); - for (File srcFile : srcFiles) { - File copiedFile = new File(destDir, srcFile.getName()); - exclusionList.add(copiedFile.getCanonicalPath()); - } - } - } - doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList); - } - - /** - * Internal copy directory method. - * - * @param srcDir the validated source directory, must not be {@code null} - * @param destDir the validated destination directory, must not be {@code null} - * @param filter the filter to apply, null means copy all directories and files - * @param preserveFileDate whether to preserve the file date - * @param exclusionList List of files and directories to exclude from the copy, may be null - * @throws java.io.IOException if an error occurs - * @since 1.1 - */ - private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter, - boolean preserveFileDate, List exclusionList) throws IOException { - // recurse - File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); - if (srcFiles == null) { // null if abstract pathname does not denote a directory, or if an I/O error occurs - throw new IOException("Failed to list contents of " + srcDir); - } - if (destDir.exists()) { - if (destDir.isDirectory() == false) { - throw new IOException("Destination '" + destDir + "' exists but is not a directory"); - } - } else { - if (!destDir.mkdirs() && !destDir.isDirectory()) { - throw new IOException("Destination '" + destDir + "' directory cannot be created"); - } - } - if (destDir.canWrite() == false) { - throw new IOException("Destination '" + destDir + "' cannot be written to"); - } - for (File srcFile : srcFiles) { - File dstFile = new File(destDir, srcFile.getName()); - if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) { - if (srcFile.isDirectory()) { - doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList); - } else { - doCopyFile(srcFile, dstFile, preserveFileDate); - } - } - } - - // Do this last, as the above has probably affected directory metadata - if (preserveFileDate) { - destDir.setLastModified(srcDir.lastModified()); - } - } - - //----------------------------------------------------------------------- - - /** - * Copies bytes from the URL source to a file - * destination. The directories up to destination - * will be created if they don't already exist. destination - * will be overwritten if it already exists. - *

    - * Warning: this method does not set a connection or read timeout and thus - * might block forever. Use {@link #copyURLToFile(java.net.URL, java.io.File, int, int)} - * with reasonable timeouts to prevent this. - * - * @param source the URL to copy bytes from, must not be {@code null} - * @param destination the non-directory File to write bytes to - * (possibly overwriting), must not be {@code null} - * @throws java.io.IOException if source URL cannot be opened - * @throws java.io.IOException if destination is a directory - * @throws java.io.IOException if destination cannot be written - * @throws java.io.IOException if destination needs creating but can't be - * @throws java.io.IOException if an IO error occurs during copying - */ - public static void copyURLToFile(URL source, File destination) throws IOException { - InputStream input = source.openStream(); - copyInputStreamToFile(input, destination); - } - - /** - * Copies bytes from the URL source to a file - * destination. The directories up to destination - * will be created if they don't already exist. destination - * will be overwritten if it already exists. - * - * @param source the URL to copy bytes from, must not be {@code null} - * @param destination the non-directory File to write bytes to - * (possibly overwriting), must not be {@code null} - * @param connectionTimeout the number of milliseconds until this method - * will timeout if no connection could be established to the source - * @param readTimeout the number of milliseconds until this method will - * timeout if no data could be read from the source - * @throws java.io.IOException if source URL cannot be opened - * @throws java.io.IOException if destination is a directory - * @throws java.io.IOException if destination cannot be written - * @throws java.io.IOException if destination needs creating but can't be - * @throws java.io.IOException if an IO error occurs during copying - * @since 2.0 - */ - public static void copyURLToFile(URL source, File destination, - int connectionTimeout, int readTimeout) throws IOException { - URLConnection connection = source.openConnection(); - connection.setConnectTimeout(connectionTimeout); - connection.setReadTimeout(readTimeout); - InputStream input = connection.getInputStream(); - copyInputStreamToFile(input, destination); - } - - /** - * Copies bytes from an {@link java.io.InputStream} source to a file - * destination. The directories up to destination - * will be created if they don't already exist. destination - * will be overwritten if it already exists. - * - * @param source the InputStream to copy bytes from, must not be {@code null} - * @param destination the non-directory File to write bytes to - * (possibly overwriting), must not be {@code null} - * @throws java.io.IOException if destination is a directory - * @throws java.io.IOException if destination cannot be written - * @throws java.io.IOException if destination needs creating but can't be - * @throws java.io.IOException if an IO error occurs during copying - * @since 2.0 - */ - public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { - try { - FileOutputStream output = openOutputStream(destination); - try { - IOUtils.copy(source, output); - output.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(output); - } - } finally { - IOUtils.closeQuietly(source); - } - } - - //----------------------------------------------------------------------- - - /** - * Deletes a directory recursively. - * - * @param directory directory to delete - * @throws java.io.IOException in case deletion is unsuccessful - */ - public static void deleteDirectory(File directory) throws IOException { - if (!directory.exists()) { - return; - } - - if (!isSymlink(directory)) { - cleanDirectory(directory); - } - - if (!directory.delete()) { - String message = - "Unable to delete directory " + directory + "."; - throw new IOException(message); - } - } - - /** - * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. - *

    - * The difference between File.delete() and this method are: - *

      - *
    • A directory to be deleted does not have to be empty.
    • - *
    • No exceptions are thrown when a file or directory cannot be deleted.
    • - *
    - * - * @param file file or directory to delete, can be {@code null} - * @return {@code true} if the file or directory was deleted, otherwise - * {@code false} - * @since 1.4 - */ - public static boolean deleteQuietly(File file) { - if (file == null) { - return false; - } - try { - if (file.isDirectory()) { - cleanDirectory(file); - } - } catch (Exception ignored) { - } - - try { - return file.delete(); - } catch (Exception ignored) { - return false; - } - } - - /** - * Cleans a directory without deleting it. - * - * @param directory directory to clean - * @throws java.io.IOException in case cleaning is unsuccessful - */ - public static void cleanDirectory(File directory) throws IOException { - if (!directory.exists()) { - String message = directory + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (!directory.isDirectory()) { - String message = directory + " is not a directory"; - throw new IllegalArgumentException(message); - } - - File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - throw new IOException("Failed to list contents of " + directory); - } - - IOException exception = null; - for (File file : files) { - try { - forceDelete(file); - } catch (IOException ioe) { - exception = ioe; - } - } - - if (null != exception) { - throw exception; - } - } - - //----------------------------------------------------------------------- - - /** - * Waits for NFS to propagate a file creation, imposing a timeout. - *

    - * This method repeatedly tests {@link java.io.File#exists()} until it returns - * true up to the maximum time specified in seconds. - * - * @param file the file to check, must not be {@code null} - * @param seconds the maximum time in seconds to wait - * @return true if file exists - * @throws NullPointerException if the file is {@code null} - */ - public static boolean waitFor(File file, int seconds) { - int timeout = 0; - int tick = 0; - while (!file.exists()) { - if (tick++ >= 10) { - tick = 0; - if (timeout++ > seconds) { - return false; - } - } - try { - Thread.sleep(100); - } catch (InterruptedException ignore) { - // ignore exception - } catch (Exception ex) { - break; - } - } - return true; - } - - //----------------------------------------------------------------------- - - /** - * Reads the contents of a file into a String. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static String readFileToString(File file, Charset encoding) throws IOException { - InputStream in = null; - try { - in = openInputStream(file); - return IOUtils.toString(in, Charsets.toCharset(encoding)); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Reads the contents of a file into a String. The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.3 - */ - public static String readFileToString(File file, String encoding) throws IOException { - return readFileToString(file, Charsets.toCharset(encoding)); - } - - - /** - * Reads the contents of a file into a String using the default encoding for the VM. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 1.3.1 - */ - public static String readFileToString(File file) throws IOException { - return readFileToString(file, Charset.defaultCharset()); - } - - /** - * Reads the contents of a file into a byte array. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @return the file contents, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 1.1 - */ - public static byte[] readFileToByteArray(File file) throws IOException { - InputStream in = null; - try { - in = openInputStream(file); - return IOUtils.toByteArray(in, file.length()); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Reads the contents of a file line by line to a List of Strings. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the list of Strings representing each line in the file, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static List readLines(File file, Charset encoding) throws IOException { - InputStream in = null; - try { - in = openInputStream(file); - return IOUtils.readLines(in, Charsets.toCharset(encoding)); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Reads the contents of a file line by line to a List of Strings. The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @param encoding the encoding to use, {@code null} means platform default - * @return the list of Strings representing each line in the file, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static List readLines(File file, String encoding) throws IOException { - return readLines(file, Charsets.toCharset(encoding)); - } - - /** - * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM. - * The file is always closed. - * - * @param file the file to read, must not be {@code null} - * @return the list of Strings representing each line in the file, never {@code null} - * @throws java.io.IOException in case of an I/O error - * @since 1.3 - */ - public static List readLines(File file) throws IOException { - return readLines(file, Charset.defaultCharset()); - } - - //----------------------------------------------------------------------- - - /** - * Writes a String to a file creating the file if it does not exist. - *

    - * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.4 - */ - public static void writeStringToFile(File file, String data, Charset encoding) throws IOException { - writeStringToFile(file, data, encoding, false); - } - - /** - * Writes a String to a file creating the file if it does not exist. - *

    - * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - */ - public static void writeStringToFile(File file, String data, String encoding) throws IOException { - writeStringToFile(file, data, encoding, false); - } - - /** - * Writes a String to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException { - OutputStream out = null; - try { - out = openOutputStream(file, append); - IOUtils.write(data, out, encoding); - out.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Writes a String to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported by the VM - * @since 2.1 - */ - public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException { - writeStringToFile(file, data, Charsets.toCharset(encoding), append); - } - - /** - * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @throws java.io.IOException in case of an I/O error - */ - public static void writeStringToFile(File file, String data) throws IOException { - writeStringToFile(file, data, Charset.defaultCharset(), false); - } - - /** - * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @param append if {@code true}, then the String will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void writeStringToFile(File file, String data, boolean append) throws IOException { - writeStringToFile(file, data, Charset.defaultCharset(), append); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @throws java.io.IOException in case of an I/O error - * @since 2.0 - */ - public static void write(File file, CharSequence data) throws IOException { - write(file, data, Charset.defaultCharset(), false); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. - * - * @param file the file to write - * @param data the content to write to the file - * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void write(File file, CharSequence data, boolean append) throws IOException { - write(file, data, Charset.defaultCharset(), append); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static void write(File file, CharSequence data, Charset encoding) throws IOException { - write(file, data, encoding, false); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.0 - */ - public static void write(File file, CharSequence data, String encoding) throws IOException { - write(file, data, encoding, false); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.3 - */ - public static void write(File file, CharSequence data, Charset encoding, boolean append) throws IOException { - String str = data == null ? null : data.toString(); - writeStringToFile(file, str, encoding, append); - } - - /** - * Writes a CharSequence to a file creating the file if it does not exist. - * - * @param file the file to write - * @param data the content to write to the file - * @param encoding the encoding to use, {@code null} means platform default - * @param append if {@code true}, then the data will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported by the VM - * @since IO 2.1 - */ - public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException { - write(file, data, Charsets.toCharset(encoding), append); - } - - /** - * Writes a byte array to a file creating the file if it does not exist. - *

    - * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write to - * @param data the content to write to the file - * @throws java.io.IOException in case of an I/O error - * @since 1.1 - */ - public static void writeByteArrayToFile(File file, byte[] data) throws IOException { - writeByteArrayToFile(file, data, false); - } - - /** - * Writes a byte array to a file creating the file if it does not exist. - * - * @param file the file to write to - * @param data the content to write to the file - * @param append if {@code true}, then bytes will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since IO 2.1 - */ - public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException { - OutputStream out = null; - try { - out = openOutputStream(file, append); - out.write(data); - out.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The specified character encoding and the default line ending will be used. - *

    - * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 1.1 - */ - public static void writeLines(File file, String encoding, Collection lines) throws IOException { - writeLines(file, encoding, lines, null, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line, optionally appending. - * The specified character encoding and the default line ending will be used. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.1 - */ - public static void writeLines(File file, String encoding, Collection lines, boolean append) throws IOException { - writeLines(file, encoding, lines, null, append); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the default line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @throws java.io.IOException in case of an I/O error - * @since 1.3 - */ - public static void writeLines(File file, Collection lines) throws IOException { - writeLines(file, null, lines, null, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the default line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void writeLines(File file, Collection lines, boolean append) throws IOException { - writeLines(file, null, lines, null, append); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The specified character encoding and the line ending will be used. - *

    - * NOTE: As from v1.3, the parent directories of the file will be created - * if they do not exist. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 1.1 - */ - public static void writeLines(File file, String encoding, Collection lines, String lineEnding) - throws IOException { - writeLines(file, encoding, lines, lineEnding, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The specified character encoding and the line ending will be used. - * - * @param file the file to write to - * @param encoding the encoding to use, {@code null} means platform default - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM - * @since 2.1 - */ - public static void writeLines(File file, String encoding, Collection lines, String lineEnding, boolean append) - throws IOException { - FileOutputStream out = null; - try { - out = openOutputStream(file, append); - final BufferedOutputStream buffer = new BufferedOutputStream(out); - IOUtils.writeLines(lines, lineEnding, buffer, encoding); - buffer.flush(); - out.close(); // don't swallow close Exception if copy completes normally - } finally { - IOUtils.closeQuietly(out); - } - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the specified line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @throws java.io.IOException in case of an I/O error - * @since 1.3 - */ - public static void writeLines(File file, Collection lines, String lineEnding) throws IOException { - writeLines(file, null, lines, lineEnding, false); - } - - /** - * Writes the toString() value of each item in a collection to - * the specified File line by line. - * The default VM encoding and the specified line ending will be used. - * - * @param file the file to write to - * @param lines the lines to write, {@code null} entries produce blank lines - * @param lineEnding the line separator to use, {@code null} is system default - * @param append if {@code true}, then the lines will be added to the - * end of the file rather than overwriting - * @throws java.io.IOException in case of an I/O error - * @since 2.1 - */ - public static void writeLines(File file, Collection lines, String lineEnding, boolean append) - throws IOException { - writeLines(file, null, lines, lineEnding, append); - } - - //----------------------------------------------------------------------- - - /** - * Deletes a file. If file is a directory, delete it and all sub-directories. - *

    - * The difference between File.delete() and this method are: - *

      - *
    • A directory to be deleted does not have to be empty.
    • - *
    • You get exceptions when a file or directory cannot be deleted. - * (java.io.File methods returns a boolean)
    • - *
    - * - * @param file file or directory to delete, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.FileNotFoundException if the file was not found - * @throws java.io.IOException in case deletion is unsuccessful - */ - public static void forceDelete(File file) throws IOException { - if (file.isDirectory()) { - deleteDirectory(file); - } else { - boolean filePresent = file.exists(); - if (!file.delete()) { - if (!filePresent) { - throw new FileNotFoundException("File does not exist: " + file); - } - String message = - "Unable to delete file: " + file; - throw new IOException(message); - } - } - } - - /** - * Schedules a file to be deleted when JVM exits. - * If file is directory delete it and all sub-directories. - * - * @param file file or directory to delete, must not be {@code null} - * @throws NullPointerException if the file is {@code null} - * @throws java.io.IOException in case deletion is unsuccessful - */ - public static void forceDeleteOnExit(File file) throws IOException { - if (file.isDirectory()) { - deleteDirectoryOnExit(file); - } else { - file.deleteOnExit(); - } - } - - /** - * Schedules a directory recursively for deletion on JVM exit. - * - * @param directory directory to delete, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.IOException in case deletion is unsuccessful - */ - private static void deleteDirectoryOnExit(File directory) throws IOException { - if (!directory.exists()) { - return; - } - - directory.deleteOnExit(); - if (!isSymlink(directory)) { - cleanDirectoryOnExit(directory); - } - } - - /** - * Cleans a directory without deleting it. - * - * @param directory directory to clean, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.IOException in case cleaning is unsuccessful - */ - private static void cleanDirectoryOnExit(File directory) throws IOException { - if (!directory.exists()) { - String message = directory + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (!directory.isDirectory()) { - String message = directory + " is not a directory"; - throw new IllegalArgumentException(message); - } - - File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - throw new IOException("Failed to list contents of " + directory); - } - - IOException exception = null; - for (File file : files) { - try { - forceDeleteOnExit(file); - } catch (IOException ioe) { - exception = ioe; - } - } - - if (null != exception) { - throw exception; - } - } - - /** - * Makes a directory, including any necessary but nonexistent parent - * directories. If a file already exists with specified name but it is - * not a directory then an IOException is thrown. - * If the directory cannot be created (or does not already exist) - * then an IOException is thrown. - * - * @param directory directory to create, must not be {@code null} - * @throws NullPointerException if the directory is {@code null} - * @throws java.io.IOException if the directory cannot be created or the file already exists but is not a directory - */ - public static void forceMkdir(File directory) throws IOException { - if (directory.exists()) { - if (!directory.isDirectory()) { - String message = - "File " - + directory - + " exists and is " - + "not a directory. Unable to create directory."; - throw new IOException(message); - } - } else { - if (!directory.mkdirs()) { - // Double-check that some other thread or process hasn't made - // the directory in the background - if (!directory.isDirectory()) { - String message = - "Unable to create directory " + directory; - throw new IOException(message); - } - } - } - } - - //----------------------------------------------------------------------- - - /** - * Returns the size of the specified file or directory. If the provided - * {@link java.io.File} is a regular file, then the file's length is returned. - * If the argument is a directory, then the size of the directory is - * calculated recursively. If a directory or subdirectory is security - * restricted, its size will not be included. - * - * @param file the regular file or directory to return the size - * of (must not be {@code null}). - * @return the length of the file, or recursive size of the directory, - * provided (in bytes). - * @throws NullPointerException if the file is {@code null} - * @throws IllegalArgumentException if the file does not exist. - * @since 2.0 - */ - public static long sizeOf(File file) { - - if (!file.exists()) { - String message = file + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (file.isDirectory()) { - return sizeOfDirectory(file); - } else { - return file.length(); - } - - } - - /** - * Returns the size of the specified file or directory. If the provided - * {@link java.io.File} is a regular file, then the file's length is returned. - * If the argument is a directory, then the size of the directory is - * calculated recursively. If a directory or subdirectory is security - * restricted, its size will not be included. - * - * @param file the regular file or directory to return the size - * of (must not be {@code null}). - * @return the length of the file, or recursive size of the directory, - * provided (in bytes). - * @throws NullPointerException if the file is {@code null} - * @throws IllegalArgumentException if the file does not exist. - * @since 2.4 - */ - public static BigInteger sizeOfAsBigInteger(File file) { - - if (!file.exists()) { - String message = file + " does not exist"; - throw new IllegalArgumentException(message); - } - - if (file.isDirectory()) { - return sizeOfDirectoryAsBigInteger(file); - } else { - return BigInteger.valueOf(file.length()); - } - - } - - /** - * Counts the size of a directory recursively (sum of the length of all files). - * - * @param directory directory to inspect, must not be {@code null} - * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total - * is greater than {@link Long#MAX_VALUE}. - * @throws NullPointerException if the directory is {@code null} - */ - public static long sizeOfDirectory(File directory) { - checkDirectory(directory); - - final File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - return 0L; - } - long size = 0; - - for (final File file : files) { - try { - if (!isSymlink(file)) { - size += sizeOf(file); - if (size < 0) { - break; - } - } - } catch (IOException ioe) { - // Ignore exceptions caught when asking if a File is a symlink. - } - } - - return size; - } - - /** - * Counts the size of a directory recursively (sum of the length of all files). - * - * @param directory directory to inspect, must not be {@code null} - * @return size of directory in bytes, 0 if directory is security restricted. - * @throws NullPointerException if the directory is {@code null} - * @since 2.4 - */ - public static BigInteger sizeOfDirectoryAsBigInteger(File directory) { - checkDirectory(directory); - - final File[] files = directory.listFiles(); - if (files == null) { // null if security restricted - return BigInteger.ZERO; - } - BigInteger size = BigInteger.ZERO; - - for (final File file : files) { - try { - if (!isSymlink(file)) { - size = size.add(BigInteger.valueOf(sizeOf(file))); - } - } catch (IOException ioe) { - // Ignore exceptions caught when asking if a File is a symlink. - } - } - - return size; - } - - /** - * Checks that the given {@code File} exists and is a directory. - * - * @param directory The {@code File} to check. - * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory. - */ - private static void checkDirectory(File directory) { - if (!directory.exists()) { - throw new IllegalArgumentException(directory + " does not exist"); - } - if (!directory.isDirectory()) { - throw new IllegalArgumentException(directory + " is not a directory"); - } - } - - //----------------------------------------------------------------------- - - /** - * Tests if the specified File is newer than the reference - * File. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param reference the File of which the modification date - * is used, must not be {@code null} - * @return true if the File exists and has been modified more - * recently than the reference File - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist - */ - public static boolean isFileNewer(File file, File reference) { - if (reference == null) { - throw new IllegalArgumentException("No specified reference file"); - } - if (!reference.exists()) { - throw new IllegalArgumentException("The reference file '" - + reference + "' doesn't exist"); - } - return isFileNewer(file, reference.lastModified()); - } - - /** - * Tests if the specified File is newer than the specified - * Date. - * - * @param file the File of which the modification date - * must be compared, must not be {@code null} - * @param date the date reference, must not be {@code null} - * @return true if the File exists and has been modified - * after the given Date. - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the date is {@code null} - */ - public static boolean isFileNewer(File file, Date date) { - if (date == null) { - throw new IllegalArgumentException("No specified date"); - } - return isFileNewer(file, date.getTime()); - } - - /** - * Tests if the specified File is newer than the specified - * time reference. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param timeMillis the time reference measured in milliseconds since the - * epoch (00:00:00 GMT, January 1, 1970) - * @return true if the File exists and has been modified after - * the given time reference. - * @throws IllegalArgumentException if the file is {@code null} - */ - public static boolean isFileNewer(File file, long timeMillis) { - if (file == null) { - throw new IllegalArgumentException("No specified file"); - } - if (!file.exists()) { - return false; - } - return file.lastModified() > timeMillis; - } - - - //----------------------------------------------------------------------- - - /** - * Tests if the specified File is older than the reference - * File. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param reference the File of which the modification date - * is used, must not be {@code null} - * @return true if the File exists and has been modified before - * the reference File - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist - */ - public static boolean isFileOlder(File file, File reference) { - if (reference == null) { - throw new IllegalArgumentException("No specified reference file"); - } - if (!reference.exists()) { - throw new IllegalArgumentException("The reference file '" - + reference + "' doesn't exist"); - } - return isFileOlder(file, reference.lastModified()); - } - - /** - * Tests if the specified File is older than the specified - * Date. - * - * @param file the File of which the modification date - * must be compared, must not be {@code null} - * @param date the date reference, must not be {@code null} - * @return true if the File exists and has been modified - * before the given Date. - * @throws IllegalArgumentException if the file is {@code null} - * @throws IllegalArgumentException if the date is {@code null} - */ - public static boolean isFileOlder(File file, Date date) { - if (date == null) { - throw new IllegalArgumentException("No specified date"); - } - return isFileOlder(file, date.getTime()); - } - - /** - * Tests if the specified File is older than the specified - * time reference. - * - * @param file the File of which the modification date must - * be compared, must not be {@code null} - * @param timeMillis the time reference measured in milliseconds since the - * epoch (00:00:00 GMT, January 1, 1970) - * @return true if the File exists and has been modified before - * the given time reference. - * @throws IllegalArgumentException if the file is {@code null} - */ - public static boolean isFileOlder(File file, long timeMillis) { - if (file == null) { - throw new IllegalArgumentException("No specified file"); - } - if (!file.exists()) { - return false; - } - return file.lastModified() < timeMillis; - } - - //----------------------------------------------------------------------- - - /** - * Moves a directory. - *

    - * When the destination directory is on another file system, do a "copy and delete". - * - * @param srcDir the directory to be moved - * @param destDir the destination directory - * @throws NullPointerException if source or destination is {@code null} - * @throws FileExistsException if the destination directory exists - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveDirectory(File srcDir, File destDir) throws IOException { - if (srcDir == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (!srcDir.exists()) { - throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); - } - if (!srcDir.isDirectory()) { - throw new IOException("Source '" + srcDir + "' is not a directory"); - } - if (destDir.exists()) { - throw new FileExistsException("Destination '" + destDir + "' already exists"); - } - boolean rename = srcDir.renameTo(destDir); - if (!rename) { - if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { - throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir); - } - copyDirectory(srcDir, destDir); - deleteDirectory(srcDir); - if (srcDir.exists()) { - throw new IOException("Failed to delete original directory '" + srcDir + - "' after copy to '" + destDir + "'"); - } - } - } - - /** - * Moves a directory to another directory. - * - * @param src the file to be moved - * @param destDir the destination file - * @param createDestDir If {@code true} create the destination directory, - * otherwise if {@code false} throw an IOException - * @throws NullPointerException if source or destination is {@code null} - * @throws FileExistsException if the directory exists in the destination directory - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException { - if (src == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination directory must not be null"); - } - if (!destDir.exists() && createDestDir) { - destDir.mkdirs(); - } - if (!destDir.exists()) { - throw new FileNotFoundException("Destination directory '" + destDir + - "' does not exist [createDestDir=" + createDestDir + "]"); - } - if (!destDir.isDirectory()) { - throw new IOException("Destination '" + destDir + "' is not a directory"); - } - moveDirectory(src, new File(destDir, src.getName())); - - } - - /** - * Moves a file. - *

    - * When the destination file is on another file system, do a "copy and delete". - * - * @param srcFile the file to be moved - * @param destFile the destination file - * @throws NullPointerException if source or destination is {@code null} - * @throws FileExistsException if the destination file exists - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveFile(File srcFile, File destFile) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destFile == null) { - throw new NullPointerException("Destination must not be null"); - } - if (!srcFile.exists()) { - throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); - } - if (srcFile.isDirectory()) { - throw new IOException("Source '" + srcFile + "' is a directory"); - } - if (destFile.exists()) { - throw new FileExistsException("Destination '" + destFile + "' already exists"); - } - if (destFile.isDirectory()) { - throw new IOException("Destination '" + destFile + "' is a directory"); - } - boolean rename = srcFile.renameTo(destFile); - if (!rename) { - copyFile(srcFile, destFile); - if (!srcFile.delete()) { - FileUtils.deleteQuietly(destFile); - throw new IOException("Failed to delete original file '" + srcFile + - "' after copy to '" + destFile + "'"); - } - } - } - - /** - * Moves a file to a directory. - * - * @param srcFile the file to be moved - * @param destDir the destination file - * @param createDestDir If {@code true} create the destination directory, - * otherwise if {@code false} throw an IOException - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException { - if (srcFile == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination directory must not be null"); - } - if (!destDir.exists() && createDestDir) { - destDir.mkdirs(); - } - if (!destDir.exists()) { - throw new FileNotFoundException("Destination directory '" + destDir + - "' does not exist [createDestDir=" + createDestDir + "]"); - } - if (!destDir.isDirectory()) { - throw new IOException("Destination '" + destDir + "' is not a directory"); - } - moveFile(srcFile, new File(destDir, srcFile.getName())); - } - - /** - * Moves a file or directory to the destination directory. - *

    - * When the destination is on another file system, do a "copy and delete". - * - * @param src the file or directory to be moved - * @param destDir the destination directory - * @param createDestDir If {@code true} create the destination directory, - * otherwise if {@code false} throw an IOException - * @throws NullPointerException if source or destination is {@code null} - * @throws java.io.IOException if source or destination is invalid - * @throws java.io.IOException if an IO error occurs moving the file - * @since 1.4 - */ - public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException { - if (src == null) { - throw new NullPointerException("Source must not be null"); - } - if (destDir == null) { - throw new NullPointerException("Destination must not be null"); - } - if (!src.exists()) { - throw new FileNotFoundException("Source '" + src + "' does not exist"); - } - if (src.isDirectory()) { - moveDirectoryToDirectory(src, destDir, createDestDir); - } else { - moveFileToDirectory(src, destDir, createDestDir); - } - } - - /** - * Determines whether the specified file is a Symbolic Link rather than an actual file. - *

    - * Will not return true if there is a Symbolic Link anywhere in the path, - * only if the specific file is. - *

    - * Note: the current implementation always returns {@code false} if the system - * is detected as Windows using {@link FilenameUtils#isSystemWindows()} - * - * @param file the file to check - * @return true if the file is a Symbolic Link - * @throws java.io.IOException if an IO error occurs while checking the file - * @since 2.0 - */ - public static boolean isSymlink(File file) throws IOException { - if (file == null) { - throw new NullPointerException("File must not be null"); - } - if (FilenameUtils.isSystemWindows()) { - return false; - } - File fileInCanonicalDir = null; - if (file.getParent() == null) { - fileInCanonicalDir = file; - } else { - File canonicalDir = file.getParentFile().getCanonicalFile(); - fileInCanonicalDir = new File(canonicalDir, file.getName()); - } - - if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { - return false; - } else { - return true; - } - } - - /** - * Indicates that a file already exists. - * - * @version $Id: FileExistsException.java 1304052 2012-03-22 20:55:29Z ggregory $ - * @since 2.0 - */ - public static class FileExistsException extends IOException { - - /** - * Defines the serial version UID. - */ - private static final long serialVersionUID = 1L; - - /** - * Default Constructor. - */ - public FileExistsException() { - super(); - } - - /** - * Construct an instance with the specified message. - * - * @param message The error message - */ - public FileExistsException(String message) { - super(message); - } - - /** - * Construct an instance with the specified file. - * - * @param file The file that exists - */ - public FileExistsException(File file) { - super("File " + file + " exists"); - } - - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import java.io.*; +import java.math.BigInteger; +import java.net.URL; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; +import java.util.*; + + +/** + * General file manipulation utilities. + *

    + * Facilities are provided in the following areas: + *

      + *
    • writing to a file + *
    • reading from a file + *
    • make a directory including parent directories + *
    • copying files and directories + *
    • deleting files and directories + *
    • converting to and from a URL + *
    • listing files and directories by filter and extension + *
    • comparing file content + *
    • file last changed date + *
    • calculating a checksum + *
    + *

    + * Origin of code: Excalibur, Alexandria, Commons-Utils + * + * @version $Id: FileUtils.java 1349509 2012-06-12 20:39:23Z ggregory $ + */ +public class FileUtils { + + /** + * Instances should NOT be constructed in standard programming. + */ + public FileUtils() { + super(); + } + + /** + * The number of bytes in a kilobyte. + */ + public static final long ONE_KB = 1024; + + /** + * The number of bytes in a kilobyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_KB_BI = BigInteger.valueOf(ONE_KB); + + /** + * The number of bytes in a megabyte. + */ + public static final long ONE_MB = ONE_KB * ONE_KB; + + /** + * The number of bytes in a megabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_MB_BI = ONE_KB_BI.multiply(ONE_KB_BI); + + /** + * The file copy buffer size (30 MB) + */ + private static final long FILE_COPY_BUFFER_SIZE = ONE_MB * 30; + + /** + * The number of bytes in a gigabyte. + */ + public static final long ONE_GB = ONE_KB * ONE_MB; + + /** + * The number of bytes in a gigabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_GB_BI = ONE_KB_BI.multiply(ONE_MB_BI); + + /** + * The number of bytes in a terabyte. + */ + public static final long ONE_TB = ONE_KB * ONE_GB; + + /** + * The number of bytes in a terabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_TB_BI = ONE_KB_BI.multiply(ONE_GB_BI); + + /** + * The number of bytes in a petabyte. + */ + public static final long ONE_PB = ONE_KB * ONE_TB; + + /** + * The number of bytes in a petabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_PB_BI = ONE_KB_BI.multiply(ONE_TB_BI); + + /** + * The number of bytes in an exabyte. + */ + public static final long ONE_EB = ONE_KB * ONE_PB; + + /** + * The number of bytes in an exabyte. + * + * @since 2.4 + */ + public static final BigInteger ONE_EB_BI = ONE_KB_BI.multiply(ONE_PB_BI); + + /** + * The number of bytes in a zettabyte. + */ + public static final BigInteger ONE_ZB = BigInteger.valueOf(ONE_KB).multiply(BigInteger.valueOf(ONE_EB)); + + /** + * The number of bytes in a yottabyte. + */ + public static final BigInteger ONE_YB = ONE_KB_BI.multiply(ONE_ZB); + + /** + * An empty array of type File. + */ + public static final File[] EMPTY_FILE_ARRAY = new File[0]; + + /** + * The UTF-8 character set, used to decode octets in URLs. + */ + private static final Charset UTF8 = Charset.forName("UTF-8"); + + //----------------------------------------------------------------------- + + /** + * Construct a file from the set of name elements. + * + * @param directory the parent directory + * @param names the name elements + * @return the file + * @since 2.1 + */ + public static File getFile(File directory, String... names) { + if (directory == null) { + throw new NullPointerException("directorydirectory must not be null"); + } + if (names == null) { + throw new NullPointerException("names must not be null"); + } + File file = directory; + for (String name : names) { + file = new File(file, name); + } + return file; + } + + /** + * Construct a file from the set of name elements. + * + * @param names the name elements + * @return the file + * @since 2.1 + */ + public static File getFile(String... names) { + if (names == null) { + throw new NullPointerException("names must not be null"); + } + File file = null; + for (String name : names) { + if (file == null) { + file = new File(name); + } else { + file = new File(file, name); + } + } + return file; + } + + /** + * Returns the path to the system temporary directory. + * + * @return the path to the system temporary directory. + * @since 2.0 + */ + public static String getTempDirectoryPath() { + return System.getProperty("java.io.tmpdir"); + } + + /** + * Returns a {@link java.io.File} representing the system temporary directory. + * + * @return the system temporary directory. + * @since 2.0 + */ + public static File getTempDirectory() { + return new File(getTempDirectoryPath()); + } + + /** + * Returns the path to the user's home directory. + * + * @return the path to the user's home directory. + * @since 2.0 + */ + public static String getUserDirectoryPath() { + return System.getProperty("user.home"); + } + + /** + * Returns a {@link java.io.File} representing the user's home directory. + * + * @return the user's home directory. + * @since 2.0 + */ + public static File getUserDirectory() { + return new File(getUserDirectoryPath()); + } + + //----------------------------------------------------------------------- + + /** + * Opens a {@link java.io.FileInputStream} for the specified file, providing better + * error messages than simply calling new FileInputStream(file). + *

    + * At the end of the method either the stream will be successfully opened, + * or an exception will have been thrown. + *

    + * An exception is thrown if the file does not exist. + * An exception is thrown if the file object exists but is a directory. + * An exception is thrown if the file exists but cannot be read. + * + * @param file the file to open for input, must not be {@code null} + * @return a new {@link java.io.FileInputStream} for the specified file + * @throws java.io.FileNotFoundException if the file does not exist + * @throws java.io.IOException if the file object is a directory + * @throws java.io.IOException if the file cannot be read + * @since 1.3 + */ + public static FileInputStream openInputStream(File file) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + if (file.canRead() == false) { + throw new IOException("File '" + file + "' cannot be read"); + } + } else { + throw new FileNotFoundException("File '" + file + "' does not exist"); + } + return new FileInputStream(file); + } + + //----------------------------------------------------------------------- + + /** + * Opens a {@link java.io.FileOutputStream} for the specified file, checking and + * creating the parent directory if it does not exist. + *

    + * At the end of the method either the stream will be successfully opened, + * or an exception will have been thrown. + *

    + * The parent directory will be created if it does not exist. + * The file will be created if it does not exist. + * An exception is thrown if the file object exists but is a directory. + * An exception is thrown if the file exists but cannot be written to. + * An exception is thrown if the parent directory cannot be created. + * + * @param file the file to open for output, must not be {@code null} + * @return a new {@link java.io.FileOutputStream} for the specified file + * @throws java.io.IOException if the file object is a directory + * @throws java.io.IOException if the file cannot be written to + * @throws java.io.IOException if a parent directory needs creating but that fails + * @since 1.3 + */ + public static FileOutputStream openOutputStream(File file) throws IOException { + return openOutputStream(file, false); + } + + /** + * Opens a {@link java.io.FileOutputStream} for the specified file, checking and + * creating the parent directory if it does not exist. + *

    + * At the end of the method either the stream will be successfully opened, + * or an exception will have been thrown. + *

    + * The parent directory will be created if it does not exist. + * The file will be created if it does not exist. + * An exception is thrown if the file object exists but is a directory. + * An exception is thrown if the file exists but cannot be written to. + * An exception is thrown if the parent directory cannot be created. + * + * @param file the file to open for output, must not be {@code null} + * @param append if {@code true}, then bytes will be added to the + * end of the file rather than overwriting + * @return a new {@link java.io.FileOutputStream} for the specified file + * @throws java.io.IOException if the file object is a directory + * @throws java.io.IOException if the file cannot be written to + * @throws java.io.IOException if a parent directory needs creating but that fails + * @since 2.1 + */ + public static FileOutputStream openOutputStream(File file, boolean append) throws IOException { + if (file.exists()) { + if (file.isDirectory()) { + throw new IOException("File '" + file + "' exists but is a directory"); + } + if (file.canWrite() == false) { + throw new IOException("File '" + file + "' cannot be written to"); + } + } else { + File parent = file.getParentFile(); + if (parent != null) { + if (!parent.mkdirs() && !parent.isDirectory()) { + throw new IOException("Directory '" + parent + "' could not be created"); + } + } + } + return new FileOutputStream(file, append); + } + + //----------------------------------------------------------------------- + + /** + * Returns a human-readable version of the file size, where the input represents a specific number of bytes. + *

    + * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the + * nearest GB boundary. + *

    + *

    + * Similarly for the 1MB and 1KB boundaries. + *

    + * + * @param size the number of bytes + * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) + * @see IO-226 - should the rounding be changed? + * @since 2.4 + */ + // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? + public static String byteCountToDisplaySize(BigInteger size) { + String displaySize; + + if (size.divide(ONE_EB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_EB_BI)) + " EB"; + } else if (size.divide(ONE_PB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_PB_BI)) + " PB"; + } else if (size.divide(ONE_TB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_TB_BI)) + " TB"; + } else if (size.divide(ONE_GB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_GB_BI)) + " GB"; + } else if (size.divide(ONE_MB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_MB_BI)) + " MB"; + } else if (size.divide(ONE_KB_BI).compareTo(BigInteger.ZERO) > 0) { + displaySize = String.valueOf(size.divide(ONE_KB_BI)) + " KB"; + } else { + displaySize = String.valueOf(size) + " bytes"; + } + return displaySize; + } + + /** + * Returns a human-readable version of the file size, where the input represents a specific number of bytes. + *

    + * If the size is over 1GB, the size is returned as the number of whole GB, i.e. the size is rounded down to the + * nearest GB boundary. + *

    + *

    + * Similarly for the 1MB and 1KB boundaries. + *

    + * + * @param size the number of bytes + * @return a human-readable display value (includes units - EB, PB, TB, GB, MB, KB or bytes) + * @see IO-226 - should the rounding be changed? + */ + // See https://issues.apache.org/jira/browse/IO-226 - should the rounding be changed? + public static String byteCountToDisplaySize(long size) { + return byteCountToDisplaySize(BigInteger.valueOf(size)); + } + + //----------------------------------------------------------------------- + + /** + * Implements the same behaviour as the "touch" utility on Unix. It creates + * a new file with size 0 or, if the file exists already, it is opened and + * closed without modifying it, but updating the file date and time. + *

    + * NOTE: As from v1.3, this method throws an IOException if the last + * modified date of the file cannot be set. Also, as from v1.3 this method + * creates parent directories if they do not exist. + * + * @param file the File to touch + * @throws java.io.IOException If an I/O problem occurs + */ + public static void touch(File file) throws IOException { + if (!file.exists()) { + OutputStream out = openOutputStream(file); + IOUtils.closeQuietly(out); + } + boolean success = file.setLastModified(System.currentTimeMillis()); + if (!success) { + throw new IOException("Unable to set the last modification time for " + file); + } + } + + //----------------------------------------------------------------------- + + /** + * Converts a Collection containing java.io.File instanced into array + * representation. This is to account for the difference between + * File.listFiles() and FileUtils.listFiles(). + * + * @param files a Collection containing java.io.File instances + * @return an array of java.io.File + */ + public static File[] convertFileCollectionToFileArray(Collection files) { + return files.toArray(new File[files.size()]); + } + + //----------------------------------------------------------------------- + + /** + * Converts an array of file extensions to suffixes for use + * with IOFileFilters. + * + * @param extensions an array of extensions. Format: {"java", "xml"} + * @return an array of suffixes. Format: {".java", ".xml"} + */ + private static String[] toSuffixes(String[] extensions) { + String[] suffixes = new String[extensions.length]; + for (int i = 0; i < extensions.length; i++) { + suffixes[i] = "." + extensions[i]; + } + return suffixes; + } + + //----------------------------------------------------------------------- + + /** + * Compares the contents of two files to determine if they are equal or not. + *

    + * This method checks to see if the two files are different lengths + * or if they point to the same file, before resorting to byte-by-byte + * comparison of the contents. + *

    + * Code origin: Avalon + * + * @param file1 the first file + * @param file2 the second file + * @return true if the content of the files are equal or they both don't + * exist, false otherwise + * @throws java.io.IOException in case of an I/O error + */ + public static boolean contentEquals(File file1, File file2) throws IOException { + boolean file1Exists = file1.exists(); + if (file1Exists != file2.exists()) { + return false; + } + + if (!file1Exists) { + // two not existing files are equal + return true; + } + + if (file1.isDirectory() || file2.isDirectory()) { + // don't want to compare directory contents + throw new IOException("Can't compare directories, only files"); + } + + if (file1.length() != file2.length()) { + // lengths differ, cannot be equal + return false; + } + + if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { + // same file + return true; + } + + InputStream input1 = null; + InputStream input2 = null; + try { + input1 = new FileInputStream(file1); + input2 = new FileInputStream(file2); + return IOUtils.contentEquals(input1, input2); + + } finally { + IOUtils.closeQuietly(input1); + IOUtils.closeQuietly(input2); + } + } + + //----------------------------------------------------------------------- + + /** + * Compares the contents of two files to determine if they are equal or not. + *

    + * This method checks to see if the two files point to the same file, + * before resorting to line-by-line comparison of the contents. + *

    + * + * @param file1 the first file + * @param file2 the second file + * @param charsetName the character encoding to be used. + * May be null, in which case the platform default is used + * @return true if the content of the files are equal or neither exists, + * false otherwise + * @throws java.io.IOException in case of an I/O error + * @see IOUtils#contentEqualsIgnoreEOL(java.io.Reader, java.io.Reader) + * @since 2.2 + */ + public static boolean contentEqualsIgnoreEOL(File file1, File file2, String charsetName) throws IOException { + boolean file1Exists = file1.exists(); + if (file1Exists != file2.exists()) { + return false; + } + + if (!file1Exists) { + // two not existing files are equal + return true; + } + + if (file1.isDirectory() || file2.isDirectory()) { + // don't want to compare directory contents + throw new IOException("Can't compare directories, only files"); + } + + if (file1.getCanonicalFile().equals(file2.getCanonicalFile())) { + // same file + return true; + } + + Reader input1 = null; + Reader input2 = null; + try { + if (charsetName == null) { + input1 = new InputStreamReader(new FileInputStream(file1)); + input2 = new InputStreamReader(new FileInputStream(file2)); + } else { + input1 = new InputStreamReader(new FileInputStream(file1), charsetName); + input2 = new InputStreamReader(new FileInputStream(file2), charsetName); + } + return IOUtils.contentEqualsIgnoreEOL(input1, input2); + + } finally { + IOUtils.closeQuietly(input1); + IOUtils.closeQuietly(input2); + } + } + + //----------------------------------------------------------------------- + + /** + * Convert from a URL to a File. + *

    + * From version 1.1 this method will decode the URL. + * Syntax such as file:///my%20docs/file.txt will be + * correctly decoded to /my docs/file.txt. Starting with version + * 1.5, this method uses UTF-8 to decode percent-encoded octets to characters. + * Additionally, malformed percent-encoded octets are handled leniently by + * passing them through literally. + * + * @param url the file URL to convert, {@code null} returns {@code null} + * @return the equivalent File object, or {@code null} + * if the URL's protocol is not file + */ + public static File toFile(URL url) { + if (url == null || !"file".equalsIgnoreCase(url.getProtocol())) { + return null; + } else { + String filename = url.getFile().replace('/', File.separatorChar); + filename = decodeUrl(filename); + return new File(filename); + } + } + + /** + * Decodes the specified URL as per RFC 3986, i.e. transforms + * percent-encoded octets to characters by decoding with the UTF-8 character + * set. This function is primarily intended for usage with + * {@link java.net.URL} which unfortunately does not enforce proper URLs. As + * such, this method will leniently accept invalid characters or malformed + * percent-encoded octets and simply pass them literally through to the + * result string. Except for rare edge cases, this will make unencoded URLs + * pass through unaltered. + * + * @param url The URL to decode, may be {@code null}. + * @return The decoded URL or {@code null} if the input was + * {@code null}. + */ + static String decodeUrl(String url) { + String decoded = url; + if (url != null && url.indexOf('%') >= 0) { + int n = url.length(); + StringBuffer buffer = new StringBuffer(); + ByteBuffer bytes = ByteBuffer.allocate(n); + for (int i = 0; i < n; ) { + if (url.charAt(i) == '%') { + try { + do { + byte octet = (byte) Integer.parseInt(url.substring(i + 1, i + 3), 16); + bytes.put(octet); + i += 3; + } while (i < n && url.charAt(i) == '%'); + continue; + } catch (RuntimeException e) { + // malformed percent-encoded octet, fall through and + // append characters literally + } finally { + if (bytes.position() > 0) { + bytes.flip(); + buffer.append(UTF8.decode(bytes).toString()); + bytes.clear(); + } + } + } + buffer.append(url.charAt(i++)); + } + decoded = buffer.toString(); + } + return decoded; + } + + /** + * Converts each of an array of URL to a File. + *

    + * Returns an array of the same size as the input. + * If the input is {@code null}, an empty array is returned. + * If the input contains {@code null}, the output array contains {@code null} at the same + * index. + *

    + * This method will decode the URL. + * Syntax such as file:///my%20docs/file.txt will be + * correctly decoded to /my docs/file.txt. + * + * @param urls the file URLs to convert, {@code null} returns empty array + * @return a non-{@code null} array of Files matching the input, with a {@code null} item + * if there was a {@code null} at that index in the input array + * @throws IllegalArgumentException if any file is not a URL file + * @throws IllegalArgumentException if any file is incorrectly encoded + * @since 1.1 + */ + public static File[] toFiles(URL[] urls) { + if (urls == null || urls.length == 0) { + return EMPTY_FILE_ARRAY; + } + File[] files = new File[urls.length]; + for (int i = 0; i < urls.length; i++) { + URL url = urls[i]; + if (url != null) { + if (url.getProtocol().equals("file") == false) { + throw new IllegalArgumentException( + "URL could not be converted to a File: " + url); + } + files[i] = toFile(url); + } + } + return files; + } + + /** + * Converts each of an array of File to a URL. + *

    + * Returns an array of the same size as the input. + * + * @param files the files to convert, must not be {@code null} + * @return an array of URLs matching the input + * @throws java.io.IOException if a file cannot be converted + * @throws NullPointerException if the parameter is null + */ + public static URL[] toURLs(File[] files) throws IOException { + URL[] urls = new URL[files.length]; + + for (int i = 0; i < urls.length; i++) { + urls[i] = files[i].toURI().toURL(); + } + + return urls; + } + + //----------------------------------------------------------------------- + + /** + * Copies a file to a directory preserving the file date. + *

    + * This method copies the contents of the specified source file + * to a file of the same name in the specified destination directory. + * The destination directory is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

    + * Note: This method tries to preserve the file's last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destDir the directory to place the copy in, must not be {@code null} + * @throws NullPointerException if source or destination is null + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFile(java.io.File, java.io.File, boolean) + */ + public static void copyFileToDirectory(File srcFile, File destDir) throws IOException { + copyFileToDirectory(srcFile, destDir, true); + } + + /** + * Copies a file to a directory optionally preserving the file date. + *

    + * This method copies the contents of the specified source file + * to a file of the same name in the specified destination directory. + * The destination directory is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

    + * Note: Setting preserveFileDate to + * {@code true} tries to preserve the file's last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destDir the directory to place the copy in, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFile(java.io.File, java.io.File, boolean) + * @since 1.3 + */ + public static void copyFileToDirectory(File srcFile, File destDir, boolean preserveFileDate) throws IOException { + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (destDir.exists() && destDir.isDirectory() == false) { + throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); + } + File destFile = new File(destDir, srcFile.getName()); + copyFile(srcFile, destFile, preserveFileDate); + } + + /** + * Copies a file to a new location preserving the file date. + *

    + * This method copies the contents of the specified source file to the + * specified destination file. The directory holding the destination file is + * created if it does not exist. If the destination file exists, then this + * method will overwrite it. + *

    + * Note: This method tries to preserve the file's last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destFile the new file, must not be {@code null} + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFileToDirectory(java.io.File, java.io.File) + */ + public static void copyFile(File srcFile, File destFile) throws IOException { + copyFile(srcFile, destFile, true); + } + + /** + * Copies a file to a new location. + *

    + * This method copies the contents of the specified source file + * to the specified destination file. + * The directory holding the destination file is created if it does not exist. + * If the destination file exists, then this method will overwrite it. + *

    + * Note: Setting preserveFileDate to + * {@code true} tries to preserve the file's last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that the operation will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcFile an existing file to copy, must not be {@code null} + * @param destFile the new file, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @see #copyFileToDirectory(java.io.File, java.io.File, boolean) + */ + public static void copyFile(File srcFile, File destFile, + boolean preserveFileDate) throws IOException { + if (srcFile == null) { + throw new NullPointerException("Source must not be null"); + } + if (destFile == null) { + throw new NullPointerException("Destination must not be null"); + } + if (srcFile.exists() == false) { + throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); + } + if (srcFile.isDirectory()) { + throw new IOException("Source '" + srcFile + "' exists but is a directory"); + } + if (srcFile.getCanonicalPath().equals(destFile.getCanonicalPath())) { + throw new IOException("Source '" + srcFile + "' and destination '" + destFile + "' are the same"); + } + File parentFile = destFile.getParentFile(); + if (parentFile != null) { + if (!parentFile.mkdirs() && !parentFile.isDirectory()) { + throw new IOException("Destination '" + parentFile + "' directory cannot be created"); + } + } + if (destFile.exists() && destFile.canWrite() == false) { + throw new IOException("Destination '" + destFile + "' exists but is read-only"); + } + doCopyFile(srcFile, destFile, preserveFileDate); + } + + /** + * Copy bytes from a File to an OutputStream. + *

    + * This method buffers the input internally, so there is no need to use a BufferedInputStream. + *

    + * + * @param input the File to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.1 + */ + public static long copyFile(File input, OutputStream output) throws IOException { + final FileInputStream fis = new FileInputStream(input); + try { + return IOUtils.copyLarge(fis, output); + } finally { + fis.close(); + } + } + + /** + * Internal copy file method. + * + * @param srcFile the validated source file, must not be {@code null} + * @param destFile the validated destination file, must not be {@code null} + * @param preserveFileDate whether to preserve the file date + * @throws java.io.IOException if an error occurs + */ + private static void doCopyFile(File srcFile, File destFile, boolean preserveFileDate) throws IOException { + if (destFile.exists() && destFile.isDirectory()) { + throw new IOException("Destination '" + destFile + "' exists but is a directory"); + } + + FileInputStream fis = null; + FileOutputStream fos = null; + FileChannel input = null; + FileChannel output = null; + try { + fis = new FileInputStream(srcFile); + fos = new FileOutputStream(destFile); + input = fis.getChannel(); + output = fos.getChannel(); + long size = input.size(); + long pos = 0; + long count = 0; + while (pos < size) { + count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos; + pos += output.transferFrom(input, pos, count); + } + } finally { + IOUtils.closeQuietly(output); + IOUtils.closeQuietly(fos); + IOUtils.closeQuietly(input); + IOUtils.closeQuietly(fis); + } + + if (srcFile.length() != destFile.length()) { + throw new IOException("Failed to copy full contents from '" + + srcFile + "' to '" + destFile + "'"); + } + if (preserveFileDate) { + destFile.setLastModified(srcFile.lastModified()); + } + } + + //----------------------------------------------------------------------- + + /** + * Copies a directory to within another directory preserving the file dates. + *

    + * This method copies the source directory and all its contents to a + * directory of the same name in the specified destination directory. + *

    + * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

    + * Note: This method tries to preserve the files' last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the directory to place the copy in, must not be {@code null} + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.2 + */ + public static void copyDirectoryToDirectory(File srcDir, File destDir) throws IOException { + if (srcDir == null) { + throw new NullPointerException("Source must not be null"); + } + if (srcDir.exists() && srcDir.isDirectory() == false) { + throw new IllegalArgumentException("Source '" + destDir + "' is not a directory"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (destDir.exists() && destDir.isDirectory() == false) { + throw new IllegalArgumentException("Destination '" + destDir + "' is not a directory"); + } + copyDirectory(srcDir, new File(destDir, srcDir.getName()), true); + } + + /** + * Copies a whole directory to a new location preserving the file dates. + *

    + * This method copies the specified directory and all its child + * directories and files to the specified destination. + * The destination is the new location and name of the directory. + *

    + * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

    + * Note: This method tries to preserve the files' last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.1 + */ + public static void copyDirectory(File srcDir, File destDir) throws IOException { + copyDirectory(srcDir, destDir, true); + } + + /** + * Copies a whole directory to a new location. + *

    + * This method copies the contents of the specified source directory + * to within the specified destination directory. + *

    + * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

    + * Note: Setting preserveFileDate to + * {@code true} tries to preserve the files' last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.1 + */ + public static void copyDirectory(File srcDir, File destDir, + boolean preserveFileDate) throws IOException { + copyDirectory(srcDir, destDir, null, preserveFileDate); + } + + /** + * Copies a filtered directory to a new location preserving the file dates. + *

    + * This method copies the contents of the specified source directory + * to within the specified destination directory. + *

    + * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

    + * Note: This method tries to preserve the files' last + * modified date/times using {@link java.io.File#setLastModified(long)}, however + * it is not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + *

    + *

    Example: Copy directories only

    + *
    +     *  // only copy the directory structure
    +     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY);
    +     *  
    + * + *

    Example: Copy directories and txt files

    + *
    +     *  // Create a filter for ".txt" files
    +     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
    +     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
    +     *
    +     *  // Create a filter for either directories or ".txt" files
    +     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
    +     *
    +     *  // Copy using the filter
    +     *  FileUtils.copyDirectory(srcDir, destDir, filter);
    +     *  
    + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @param filter the filter to apply, null means copy all directories and files + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.4 + */ + public static void copyDirectory(File srcDir, File destDir, + FileFilter filter) throws IOException { + copyDirectory(srcDir, destDir, filter, true); + } + + /** + * Copies a filtered directory to a new location. + *

    + * This method copies the contents of the specified source directory + * to within the specified destination directory. + *

    + * The destination directory is created if it does not exist. + * If the destination directory did exist, then this method merges + * the source with the destination, with the source taking precedence. + *

    + * Note: Setting preserveFileDate to + * {@code true} tries to preserve the files' last modified + * date/times using {@link java.io.File#setLastModified(long)}, however it is + * not guaranteed that those operations will succeed. + * If the modification operation fails, no indication is provided. + *

    + *

    Example: Copy directories only

    + *
    +     *  // only copy the directory structure
    +     *  FileUtils.copyDirectory(srcDir, destDir, DirectoryFileFilter.DIRECTORY, false);
    +     *  
    + * + *

    Example: Copy directories and txt files

    + *
    +     *  // Create a filter for ".txt" files
    +     *  IOFileFilter txtSuffixFilter = FileFilterUtils.suffixFileFilter(".txt");
    +     *  IOFileFilter txtFiles = FileFilterUtils.andFileFilter(FileFileFilter.FILE, txtSuffixFilter);
    +     *
    +     *  // Create a filter for either directories or ".txt" files
    +     *  FileFilter filter = FileFilterUtils.orFileFilter(DirectoryFileFilter.DIRECTORY, txtFiles);
    +     *
    +     *  // Copy using the filter
    +     *  FileUtils.copyDirectory(srcDir, destDir, filter, false);
    +     *  
    + * + * @param srcDir an existing directory to copy, must not be {@code null} + * @param destDir the new directory, must not be {@code null} + * @param filter the filter to apply, null means copy all directories and files + * @param preserveFileDate true if the file date of the copy + * should be the same as the original + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs during copying + * @since 1.4 + */ + public static void copyDirectory(File srcDir, File destDir, + FileFilter filter, boolean preserveFileDate) throws IOException { + if (srcDir == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (srcDir.exists() == false) { + throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); + } + if (srcDir.isDirectory() == false) { + throw new IOException("Source '" + srcDir + "' exists but is not a directory"); + } + if (srcDir.getCanonicalPath().equals(destDir.getCanonicalPath())) { + throw new IOException("Source '" + srcDir + "' and destination '" + destDir + "' are the same"); + } + + // Cater for destination being directory within the source directory (see IO-141) + List exclusionList = null; + if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { + File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); + if (srcFiles != null && srcFiles.length > 0) { + exclusionList = new ArrayList(srcFiles.length); + for (File srcFile : srcFiles) { + File copiedFile = new File(destDir, srcFile.getName()); + exclusionList.add(copiedFile.getCanonicalPath()); + } + } + } + doCopyDirectory(srcDir, destDir, filter, preserveFileDate, exclusionList); + } + + /** + * Internal copy directory method. + * + * @param srcDir the validated source directory, must not be {@code null} + * @param destDir the validated destination directory, must not be {@code null} + * @param filter the filter to apply, null means copy all directories and files + * @param preserveFileDate whether to preserve the file date + * @param exclusionList List of files and directories to exclude from the copy, may be null + * @throws java.io.IOException if an error occurs + * @since 1.1 + */ + private static void doCopyDirectory(File srcDir, File destDir, FileFilter filter, + boolean preserveFileDate, List exclusionList) throws IOException { + // recurse + File[] srcFiles = filter == null ? srcDir.listFiles() : srcDir.listFiles(filter); + if (srcFiles == null) { // null if abstract pathname does not denote a directory, or if an I/O error occurs + throw new IOException("Failed to list contents of " + srcDir); + } + if (destDir.exists()) { + if (destDir.isDirectory() == false) { + throw new IOException("Destination '" + destDir + "' exists but is not a directory"); + } + } else { + if (!destDir.mkdirs() && !destDir.isDirectory()) { + throw new IOException("Destination '" + destDir + "' directory cannot be created"); + } + } + if (destDir.canWrite() == false) { + throw new IOException("Destination '" + destDir + "' cannot be written to"); + } + for (File srcFile : srcFiles) { + File dstFile = new File(destDir, srcFile.getName()); + if (exclusionList == null || !exclusionList.contains(srcFile.getCanonicalPath())) { + if (srcFile.isDirectory()) { + doCopyDirectory(srcFile, dstFile, filter, preserveFileDate, exclusionList); + } else { + doCopyFile(srcFile, dstFile, preserveFileDate); + } + } + } + + // Do this last, as the above has probably affected directory metadata + if (preserveFileDate) { + destDir.setLastModified(srcDir.lastModified()); + } + } + + //----------------------------------------------------------------------- + + /** + * Copies bytes from the URL source to a file + * destination. The directories up to destination + * will be created if they don't already exist. destination + * will be overwritten if it already exists. + *

    + * Warning: this method does not set a connection or read timeout and thus + * might block forever. Use {@link #copyURLToFile(java.net.URL, java.io.File, int, int)} + * with reasonable timeouts to prevent this. + * + * @param source the URL to copy bytes from, must not be {@code null} + * @param destination the non-directory File to write bytes to + * (possibly overwriting), must not be {@code null} + * @throws java.io.IOException if source URL cannot be opened + * @throws java.io.IOException if destination is a directory + * @throws java.io.IOException if destination cannot be written + * @throws java.io.IOException if destination needs creating but can't be + * @throws java.io.IOException if an IO error occurs during copying + */ + public static void copyURLToFile(URL source, File destination) throws IOException { + InputStream input = source.openStream(); + copyInputStreamToFile(input, destination); + } + + /** + * Copies bytes from the URL source to a file + * destination. The directories up to destination + * will be created if they don't already exist. destination + * will be overwritten if it already exists. + * + * @param source the URL to copy bytes from, must not be {@code null} + * @param destination the non-directory File to write bytes to + * (possibly overwriting), must not be {@code null} + * @param connectionTimeout the number of milliseconds until this method + * will timeout if no connection could be established to the source + * @param readTimeout the number of milliseconds until this method will + * timeout if no data could be read from the source + * @throws java.io.IOException if source URL cannot be opened + * @throws java.io.IOException if destination is a directory + * @throws java.io.IOException if destination cannot be written + * @throws java.io.IOException if destination needs creating but can't be + * @throws java.io.IOException if an IO error occurs during copying + * @since 2.0 + */ + public static void copyURLToFile(URL source, File destination, + int connectionTimeout, int readTimeout) throws IOException { + URLConnection connection = source.openConnection(); + connection.setConnectTimeout(connectionTimeout); + connection.setReadTimeout(readTimeout); + InputStream input = connection.getInputStream(); + copyInputStreamToFile(input, destination); + } + + /** + * Copies bytes from an {@link java.io.InputStream} source to a file + * destination. The directories up to destination + * will be created if they don't already exist. destination + * will be overwritten if it already exists. + * + * @param source the InputStream to copy bytes from, must not be {@code null} + * @param destination the non-directory File to write bytes to + * (possibly overwriting), must not be {@code null} + * @throws java.io.IOException if destination is a directory + * @throws java.io.IOException if destination cannot be written + * @throws java.io.IOException if destination needs creating but can't be + * @throws java.io.IOException if an IO error occurs during copying + * @since 2.0 + */ + public static void copyInputStreamToFile(InputStream source, File destination) throws IOException { + try { + FileOutputStream output = openOutputStream(destination); + try { + IOUtils.copy(source, output); + output.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(output); + } + } finally { + IOUtils.closeQuietly(source); + } + } + + //----------------------------------------------------------------------- + + /** + * Deletes a directory recursively. + * + * @param directory directory to delete + * @throws java.io.IOException in case deletion is unsuccessful + */ + public static void deleteDirectory(File directory) throws IOException { + if (!directory.exists()) { + return; + } + + if (!isSymlink(directory)) { + cleanDirectory(directory); + } + + if (!directory.delete()) { + String message = + "Unable to delete directory " + directory + "."; + throw new IOException(message); + } + } + + /** + * Deletes a file, never throwing an exception. If file is a directory, delete it and all sub-directories. + *

    + * The difference between File.delete() and this method are: + *

      + *
    • A directory to be deleted does not have to be empty.
    • + *
    • No exceptions are thrown when a file or directory cannot be deleted.
    • + *
    + * + * @param file file or directory to delete, can be {@code null} + * @return {@code true} if the file or directory was deleted, otherwise + * {@code false} + * @since 1.4 + */ + public static boolean deleteQuietly(File file) { + if (file == null) { + return false; + } + try { + if (file.isDirectory()) { + cleanDirectory(file); + } + } catch (Exception ignored) { + } + + try { + return file.delete(); + } catch (Exception ignored) { + return false; + } + } + + /** + * Cleans a directory without deleting it. + * + * @param directory directory to clean + * @throws java.io.IOException in case cleaning is unsuccessful + */ + public static void cleanDirectory(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + forceDelete(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + //----------------------------------------------------------------------- + + /** + * Waits for NFS to propagate a file creation, imposing a timeout. + *

    + * This method repeatedly tests {@link java.io.File#exists()} until it returns + * true up to the maximum time specified in seconds. + * + * @param file the file to check, must not be {@code null} + * @param seconds the maximum time in seconds to wait + * @return true if file exists + * @throws NullPointerException if the file is {@code null} + */ + public static boolean waitFor(File file, int seconds) { + int timeout = 0; + int tick = 0; + while (!file.exists()) { + if (tick++ >= 10) { + tick = 0; + if (timeout++ > seconds) { + return false; + } + } + try { + Thread.sleep(100); + } catch (InterruptedException ignore) { + // ignore exception + } catch (Exception ex) { + break; + } + } + return true; + } + + //----------------------------------------------------------------------- + + /** + * Reads the contents of a file into a String. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static String readFileToString(File file, Charset encoding) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.toString(in, Charsets.toCharset(encoding)); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Reads the contents of a file into a String. The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.3 + */ + public static String readFileToString(File file, String encoding) throws IOException { + return readFileToString(file, Charsets.toCharset(encoding)); + } + + + /** + * Reads the contents of a file into a String using the default encoding for the VM. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 1.3.1 + */ + public static String readFileToString(File file) throws IOException { + return readFileToString(file, Charset.defaultCharset()); + } + + /** + * Reads the contents of a file into a byte array. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the file contents, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 1.1 + */ + public static byte[] readFileToByteArray(File file) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.toByteArray(in, file.length()); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Reads the contents of a file line by line to a List of Strings. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the list of Strings representing each line in the file, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static List readLines(File file, Charset encoding) throws IOException { + InputStream in = null; + try { + in = openInputStream(file); + return IOUtils.readLines(in, Charsets.toCharset(encoding)); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Reads the contents of a file line by line to a List of Strings. The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @param encoding the encoding to use, {@code null} means platform default + * @return the list of Strings representing each line in the file, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static List readLines(File file, String encoding) throws IOException { + return readLines(file, Charsets.toCharset(encoding)); + } + + /** + * Reads the contents of a file line by line to a List of Strings using the default encoding for the VM. + * The file is always closed. + * + * @param file the file to read, must not be {@code null} + * @return the list of Strings representing each line in the file, never {@code null} + * @throws java.io.IOException in case of an I/O error + * @since 1.3 + */ + public static List readLines(File file) throws IOException { + return readLines(file, Charset.defaultCharset()); + } + + //----------------------------------------------------------------------- + + /** + * Writes a String to a file creating the file if it does not exist. + *

    + * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.4 + */ + public static void writeStringToFile(File file, String data, Charset encoding) throws IOException { + writeStringToFile(file, data, encoding, false); + } + + /** + * Writes a String to a file creating the file if it does not exist. + *

    + * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + */ + public static void writeStringToFile(File file, String data, String encoding) throws IOException { + writeStringToFile(file, data, encoding, false); + } + + /** + * Writes a String to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the String will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static void writeStringToFile(File file, String data, Charset encoding, boolean append) throws IOException { + OutputStream out = null; + try { + out = openOutputStream(file, append); + IOUtils.write(data, out, encoding); + out.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Writes a String to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the String will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported by the VM + * @since 2.1 + */ + public static void writeStringToFile(File file, String data, String encoding, boolean append) throws IOException { + writeStringToFile(file, data, Charsets.toCharset(encoding), append); + } + + /** + * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @throws java.io.IOException in case of an I/O error + */ + public static void writeStringToFile(File file, String data) throws IOException { + writeStringToFile(file, data, Charset.defaultCharset(), false); + } + + /** + * Writes a String to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @param append if {@code true}, then the String will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void writeStringToFile(File file, String data, boolean append) throws IOException { + writeStringToFile(file, data, Charset.defaultCharset(), append); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @throws java.io.IOException in case of an I/O error + * @since 2.0 + */ + public static void write(File file, CharSequence data) throws IOException { + write(file, data, Charset.defaultCharset(), false); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist using the default encoding for the VM. + * + * @param file the file to write + * @param data the content to write to the file + * @param append if {@code true}, then the data will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void write(File file, CharSequence data, boolean append) throws IOException { + write(file, data, Charset.defaultCharset(), append); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static void write(File file, CharSequence data, Charset encoding) throws IOException { + write(file, data, encoding, false); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.0 + */ + public static void write(File file, CharSequence data, String encoding) throws IOException { + write(file, data, encoding, false); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the data will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.3 + */ + public static void write(File file, CharSequence data, Charset encoding, boolean append) throws IOException { + String str = data == null ? null : data.toString(); + writeStringToFile(file, str, encoding, append); + } + + /** + * Writes a CharSequence to a file creating the file if it does not exist. + * + * @param file the file to write + * @param data the content to write to the file + * @param encoding the encoding to use, {@code null} means platform default + * @param append if {@code true}, then the data will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported by the VM + * @since IO 2.1 + */ + public static void write(File file, CharSequence data, String encoding, boolean append) throws IOException { + write(file, data, Charsets.toCharset(encoding), append); + } + + /** + * Writes a byte array to a file creating the file if it does not exist. + *

    + * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @throws java.io.IOException in case of an I/O error + * @since 1.1 + */ + public static void writeByteArrayToFile(File file, byte[] data) throws IOException { + writeByteArrayToFile(file, data, false); + } + + /** + * Writes a byte array to a file creating the file if it does not exist. + * + * @param file the file to write to + * @param data the content to write to the file + * @param append if {@code true}, then bytes will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since IO 2.1 + */ + public static void writeByteArrayToFile(File file, byte[] data, boolean append) throws IOException { + OutputStream out = null; + try { + out = openOutputStream(file, append); + out.write(data); + out.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The specified character encoding and the default line ending will be used. + *

    + * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 1.1 + */ + public static void writeLines(File file, String encoding, Collection lines) throws IOException { + writeLines(file, encoding, lines, null, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line, optionally appending. + * The specified character encoding and the default line ending will be used. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.1 + */ + public static void writeLines(File file, String encoding, Collection lines, boolean append) throws IOException { + writeLines(file, encoding, lines, null, append); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the default line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @throws java.io.IOException in case of an I/O error + * @since 1.3 + */ + public static void writeLines(File file, Collection lines) throws IOException { + writeLines(file, null, lines, null, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the default line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void writeLines(File file, Collection lines, boolean append) throws IOException { + writeLines(file, null, lines, null, append); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The specified character encoding and the line ending will be used. + *

    + * NOTE: As from v1.3, the parent directories of the file will be created + * if they do not exist. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 1.1 + */ + public static void writeLines(File file, String encoding, Collection lines, String lineEnding) + throws IOException { + writeLines(file, encoding, lines, lineEnding, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The specified character encoding and the line ending will be used. + * + * @param file the file to write to + * @param encoding the encoding to use, {@code null} means platform default + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @throws java.io.UnsupportedEncodingException if the encoding is not supported by the VM + * @since 2.1 + */ + public static void writeLines(File file, String encoding, Collection lines, String lineEnding, boolean append) + throws IOException { + FileOutputStream out = null; + try { + out = openOutputStream(file, append); + final BufferedOutputStream buffer = new BufferedOutputStream(out); + IOUtils.writeLines(lines, lineEnding, buffer, encoding); + buffer.flush(); + out.close(); // don't swallow close Exception if copy completes normally + } finally { + IOUtils.closeQuietly(out); + } + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the specified line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @throws java.io.IOException in case of an I/O error + * @since 1.3 + */ + public static void writeLines(File file, Collection lines, String lineEnding) throws IOException { + writeLines(file, null, lines, lineEnding, false); + } + + /** + * Writes the toString() value of each item in a collection to + * the specified File line by line. + * The default VM encoding and the specified line ending will be used. + * + * @param file the file to write to + * @param lines the lines to write, {@code null} entries produce blank lines + * @param lineEnding the line separator to use, {@code null} is system default + * @param append if {@code true}, then the lines will be added to the + * end of the file rather than overwriting + * @throws java.io.IOException in case of an I/O error + * @since 2.1 + */ + public static void writeLines(File file, Collection lines, String lineEnding, boolean append) + throws IOException { + writeLines(file, null, lines, lineEnding, append); + } + + //----------------------------------------------------------------------- + + /** + * Deletes a file. If file is a directory, delete it and all sub-directories. + *

    + * The difference between File.delete() and this method are: + *

      + *
    • A directory to be deleted does not have to be empty.
    • + *
    • You get exceptions when a file or directory cannot be deleted. + * (java.io.File methods returns a boolean)
    • + *
    + * + * @param file file or directory to delete, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.FileNotFoundException if the file was not found + * @throws java.io.IOException in case deletion is unsuccessful + */ + public static void forceDelete(File file) throws IOException { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + boolean filePresent = file.exists(); + if (!file.delete()) { + if (!filePresent) { + throw new FileNotFoundException("File does not exist: " + file); + } + String message = + "Unable to delete file: " + file; + throw new IOException(message); + } + } + } + + /** + * Schedules a file to be deleted when JVM exits. + * If file is directory delete it and all sub-directories. + * + * @param file file or directory to delete, must not be {@code null} + * @throws NullPointerException if the file is {@code null} + * @throws java.io.IOException in case deletion is unsuccessful + */ + public static void forceDeleteOnExit(File file) throws IOException { + if (file.isDirectory()) { + deleteDirectoryOnExit(file); + } else { + file.deleteOnExit(); + } + } + + /** + * Schedules a directory recursively for deletion on JVM exit. + * + * @param directory directory to delete, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.IOException in case deletion is unsuccessful + */ + private static void deleteDirectoryOnExit(File directory) throws IOException { + if (!directory.exists()) { + return; + } + + directory.deleteOnExit(); + if (!isSymlink(directory)) { + cleanDirectoryOnExit(directory); + } + } + + /** + * Cleans a directory without deleting it. + * + * @param directory directory to clean, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.IOException in case cleaning is unsuccessful + */ + private static void cleanDirectoryOnExit(File directory) throws IOException { + if (!directory.exists()) { + String message = directory + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (!directory.isDirectory()) { + String message = directory + " is not a directory"; + throw new IllegalArgumentException(message); + } + + File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + throw new IOException("Failed to list contents of " + directory); + } + + IOException exception = null; + for (File file : files) { + try { + forceDeleteOnExit(file); + } catch (IOException ioe) { + exception = ioe; + } + } + + if (null != exception) { + throw exception; + } + } + + /** + * Makes a directory, including any necessary but nonexistent parent + * directories. If a file already exists with specified name but it is + * not a directory then an IOException is thrown. + * If the directory cannot be created (or does not already exist) + * then an IOException is thrown. + * + * @param directory directory to create, must not be {@code null} + * @throws NullPointerException if the directory is {@code null} + * @throws java.io.IOException if the directory cannot be created or the file already exists but is not a directory + */ + public static void forceMkdir(File directory) throws IOException { + if (directory.exists()) { + if (!directory.isDirectory()) { + String message = + "File " + + directory + + " exists and is " + + "not a directory. Unable to create directory."; + throw new IOException(message); + } + } else { + if (!directory.mkdirs()) { + // Double-check that some other thread or process hasn't made + // the directory in the background + if (!directory.isDirectory()) { + String message = + "Unable to create directory " + directory; + throw new IOException(message); + } + } + } + } + + //----------------------------------------------------------------------- + + /** + * Returns the size of the specified file or directory. If the provided + * {@link java.io.File} is a regular file, then the file's length is returned. + * If the argument is a directory, then the size of the directory is + * calculated recursively. If a directory or subdirectory is security + * restricted, its size will not be included. + * + * @param file the regular file or directory to return the size + * of (must not be {@code null}). + * @return the length of the file, or recursive size of the directory, + * provided (in bytes). + * @throws NullPointerException if the file is {@code null} + * @throws IllegalArgumentException if the file does not exist. + * @since 2.0 + */ + public static long sizeOf(File file) { + + if (!file.exists()) { + String message = file + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (file.isDirectory()) { + return sizeOfDirectory(file); + } else { + return file.length(); + } + + } + + /** + * Returns the size of the specified file or directory. If the provided + * {@link java.io.File} is a regular file, then the file's length is returned. + * If the argument is a directory, then the size of the directory is + * calculated recursively. If a directory or subdirectory is security + * restricted, its size will not be included. + * + * @param file the regular file or directory to return the size + * of (must not be {@code null}). + * @return the length of the file, or recursive size of the directory, + * provided (in bytes). + * @throws NullPointerException if the file is {@code null} + * @throws IllegalArgumentException if the file does not exist. + * @since 2.4 + */ + public static BigInteger sizeOfAsBigInteger(File file) { + + if (!file.exists()) { + String message = file + " does not exist"; + throw new IllegalArgumentException(message); + } + + if (file.isDirectory()) { + return sizeOfDirectoryAsBigInteger(file); + } else { + return BigInteger.valueOf(file.length()); + } + + } + + /** + * Counts the size of a directory recursively (sum of the length of all files). + * + * @param directory directory to inspect, must not be {@code null} + * @return size of directory in bytes, 0 if directory is security restricted, a negative number when the real total + * is greater than {@link Long#MAX_VALUE}. + * @throws NullPointerException if the directory is {@code null} + */ + public static long sizeOfDirectory(File directory) { + checkDirectory(directory); + + final File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + return 0L; + } + long size = 0; + + for (final File file : files) { + try { + if (!isSymlink(file)) { + size += sizeOf(file); + if (size < 0) { + break; + } + } + } catch (IOException ioe) { + // Ignore exceptions caught when asking if a File is a symlink. + } + } + + return size; + } + + /** + * Counts the size of a directory recursively (sum of the length of all files). + * + * @param directory directory to inspect, must not be {@code null} + * @return size of directory in bytes, 0 if directory is security restricted. + * @throws NullPointerException if the directory is {@code null} + * @since 2.4 + */ + public static BigInteger sizeOfDirectoryAsBigInteger(File directory) { + checkDirectory(directory); + + final File[] files = directory.listFiles(); + if (files == null) { // null if security restricted + return BigInteger.ZERO; + } + BigInteger size = BigInteger.ZERO; + + for (final File file : files) { + try { + if (!isSymlink(file)) { + size = size.add(BigInteger.valueOf(sizeOf(file))); + } + } catch (IOException ioe) { + // Ignore exceptions caught when asking if a File is a symlink. + } + } + + return size; + } + + /** + * Checks that the given {@code File} exists and is a directory. + * + * @param directory The {@code File} to check. + * @throws IllegalArgumentException if the given {@code File} does not exist or is not a directory. + */ + private static void checkDirectory(File directory) { + if (!directory.exists()) { + throw new IllegalArgumentException(directory + " does not exist"); + } + if (!directory.isDirectory()) { + throw new IllegalArgumentException(directory + " is not a directory"); + } + } + + //----------------------------------------------------------------------- + + /** + * Tests if the specified File is newer than the reference + * File. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param reference the File of which the modification date + * is used, must not be {@code null} + * @return true if the File exists and has been modified more + * recently than the reference File + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist + */ + public static boolean isFileNewer(File file, File reference) { + if (reference == null) { + throw new IllegalArgumentException("No specified reference file"); + } + if (!reference.exists()) { + throw new IllegalArgumentException("The reference file '" + + reference + "' doesn't exist"); + } + return isFileNewer(file, reference.lastModified()); + } + + /** + * Tests if the specified File is newer than the specified + * Date. + * + * @param file the File of which the modification date + * must be compared, must not be {@code null} + * @param date the date reference, must not be {@code null} + * @return true if the File exists and has been modified + * after the given Date. + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the date is {@code null} + */ + public static boolean isFileNewer(File file, Date date) { + if (date == null) { + throw new IllegalArgumentException("No specified date"); + } + return isFileNewer(file, date.getTime()); + } + + /** + * Tests if the specified File is newer than the specified + * time reference. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param timeMillis the time reference measured in milliseconds since the + * epoch (00:00:00 GMT, January 1, 1970) + * @return true if the File exists and has been modified after + * the given time reference. + * @throws IllegalArgumentException if the file is {@code null} + */ + public static boolean isFileNewer(File file, long timeMillis) { + if (file == null) { + throw new IllegalArgumentException("No specified file"); + } + if (!file.exists()) { + return false; + } + return file.lastModified() > timeMillis; + } + + + //----------------------------------------------------------------------- + + /** + * Tests if the specified File is older than the reference + * File. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param reference the File of which the modification date + * is used, must not be {@code null} + * @return true if the File exists and has been modified before + * the reference File + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the reference file is {@code null} or doesn't exist + */ + public static boolean isFileOlder(File file, File reference) { + if (reference == null) { + throw new IllegalArgumentException("No specified reference file"); + } + if (!reference.exists()) { + throw new IllegalArgumentException("The reference file '" + + reference + "' doesn't exist"); + } + return isFileOlder(file, reference.lastModified()); + } + + /** + * Tests if the specified File is older than the specified + * Date. + * + * @param file the File of which the modification date + * must be compared, must not be {@code null} + * @param date the date reference, must not be {@code null} + * @return true if the File exists and has been modified + * before the given Date. + * @throws IllegalArgumentException if the file is {@code null} + * @throws IllegalArgumentException if the date is {@code null} + */ + public static boolean isFileOlder(File file, Date date) { + if (date == null) { + throw new IllegalArgumentException("No specified date"); + } + return isFileOlder(file, date.getTime()); + } + + /** + * Tests if the specified File is older than the specified + * time reference. + * + * @param file the File of which the modification date must + * be compared, must not be {@code null} + * @param timeMillis the time reference measured in milliseconds since the + * epoch (00:00:00 GMT, January 1, 1970) + * @return true if the File exists and has been modified before + * the given time reference. + * @throws IllegalArgumentException if the file is {@code null} + */ + public static boolean isFileOlder(File file, long timeMillis) { + if (file == null) { + throw new IllegalArgumentException("No specified file"); + } + if (!file.exists()) { + return false; + } + return file.lastModified() < timeMillis; + } + + //----------------------------------------------------------------------- + + /** + * Moves a directory. + *

    + * When the destination directory is on another file system, do a "copy and delete". + * + * @param srcDir the directory to be moved + * @param destDir the destination directory + * @throws NullPointerException if source or destination is {@code null} + * @throws FileExistsException if the destination directory exists + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveDirectory(File srcDir, File destDir) throws IOException { + if (srcDir == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!srcDir.exists()) { + throw new FileNotFoundException("Source '" + srcDir + "' does not exist"); + } + if (!srcDir.isDirectory()) { + throw new IOException("Source '" + srcDir + "' is not a directory"); + } + if (destDir.exists()) { + throw new FileExistsException("Destination '" + destDir + "' already exists"); + } + boolean rename = srcDir.renameTo(destDir); + if (!rename) { + if (destDir.getCanonicalPath().startsWith(srcDir.getCanonicalPath())) { + throw new IOException("Cannot move directory: " + srcDir + " to a subdirectory of itself: " + destDir); + } + copyDirectory(srcDir, destDir); + deleteDirectory(srcDir); + if (srcDir.exists()) { + throw new IOException("Failed to delete original directory '" + srcDir + + "' after copy to '" + destDir + "'"); + } + } + } + + /** + * Moves a directory to another directory. + * + * @param src the file to be moved + * @param destDir the destination file + * @param createDestDir If {@code true} create the destination directory, + * otherwise if {@code false} throw an IOException + * @throws NullPointerException if source or destination is {@code null} + * @throws FileExistsException if the directory exists in the destination directory + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveDirectoryToDirectory(File src, File destDir, boolean createDestDir) throws IOException { + if (src == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination directory must not be null"); + } + if (!destDir.exists() && createDestDir) { + destDir.mkdirs(); + } + if (!destDir.exists()) { + throw new FileNotFoundException("Destination directory '" + destDir + + "' does not exist [createDestDir=" + createDestDir + "]"); + } + if (!destDir.isDirectory()) { + throw new IOException("Destination '" + destDir + "' is not a directory"); + } + moveDirectory(src, new File(destDir, src.getName())); + + } + + /** + * Moves a file. + *

    + * When the destination file is on another file system, do a "copy and delete". + * + * @param srcFile the file to be moved + * @param destFile the destination file + * @throws NullPointerException if source or destination is {@code null} + * @throws FileExistsException if the destination file exists + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveFile(File srcFile, File destFile) throws IOException { + if (srcFile == null) { + throw new NullPointerException("Source must not be null"); + } + if (destFile == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!srcFile.exists()) { + throw new FileNotFoundException("Source '" + srcFile + "' does not exist"); + } + if (srcFile.isDirectory()) { + throw new IOException("Source '" + srcFile + "' is a directory"); + } + if (destFile.exists()) { + throw new FileExistsException("Destination '" + destFile + "' already exists"); + } + if (destFile.isDirectory()) { + throw new IOException("Destination '" + destFile + "' is a directory"); + } + boolean rename = srcFile.renameTo(destFile); + if (!rename) { + copyFile(srcFile, destFile); + if (!srcFile.delete()) { + FileUtils.deleteQuietly(destFile); + throw new IOException("Failed to delete original file '" + srcFile + + "' after copy to '" + destFile + "'"); + } + } + } + + /** + * Moves a file to a directory. + * + * @param srcFile the file to be moved + * @param destDir the destination file + * @param createDestDir If {@code true} create the destination directory, + * otherwise if {@code false} throw an IOException + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveFileToDirectory(File srcFile, File destDir, boolean createDestDir) throws IOException { + if (srcFile == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination directory must not be null"); + } + if (!destDir.exists() && createDestDir) { + destDir.mkdirs(); + } + if (!destDir.exists()) { + throw new FileNotFoundException("Destination directory '" + destDir + + "' does not exist [createDestDir=" + createDestDir + "]"); + } + if (!destDir.isDirectory()) { + throw new IOException("Destination '" + destDir + "' is not a directory"); + } + moveFile(srcFile, new File(destDir, srcFile.getName())); + } + + /** + * Moves a file or directory to the destination directory. + *

    + * When the destination is on another file system, do a "copy and delete". + * + * @param src the file or directory to be moved + * @param destDir the destination directory + * @param createDestDir If {@code true} create the destination directory, + * otherwise if {@code false} throw an IOException + * @throws NullPointerException if source or destination is {@code null} + * @throws java.io.IOException if source or destination is invalid + * @throws java.io.IOException if an IO error occurs moving the file + * @since 1.4 + */ + public static void moveToDirectory(File src, File destDir, boolean createDestDir) throws IOException { + if (src == null) { + throw new NullPointerException("Source must not be null"); + } + if (destDir == null) { + throw new NullPointerException("Destination must not be null"); + } + if (!src.exists()) { + throw new FileNotFoundException("Source '" + src + "' does not exist"); + } + if (src.isDirectory()) { + moveDirectoryToDirectory(src, destDir, createDestDir); + } else { + moveFileToDirectory(src, destDir, createDestDir); + } + } + + /** + * Determines whether the specified file is a Symbolic Link rather than an actual file. + *

    + * Will not return true if there is a Symbolic Link anywhere in the path, + * only if the specific file is. + *

    + * Note: the current implementation always returns {@code false} if the system + * is detected as Windows using {@link FilenameUtils#isSystemWindows()} + * + * @param file the file to check + * @return true if the file is a Symbolic Link + * @throws java.io.IOException if an IO error occurs while checking the file + * @since 2.0 + */ + public static boolean isSymlink(File file) throws IOException { + if (file == null) { + throw new NullPointerException("File must not be null"); + } + if (FilenameUtils.isSystemWindows()) { + return false; + } + File fileInCanonicalDir = null; + if (file.getParent() == null) { + fileInCanonicalDir = file; + } else { + File canonicalDir = file.getParentFile().getCanonicalFile(); + fileInCanonicalDir = new File(canonicalDir, file.getName()); + } + + if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) { + return false; + } else { + return true; + } + } + + /** + * Indicates that a file already exists. + * + * @version $Id: FileExistsException.java 1304052 2012-03-22 20:55:29Z ggregory $ + * @since 2.0 + */ + public static class FileExistsException extends IOException { + + /** + * Defines the serial version UID. + */ + private static final long serialVersionUID = 1L; + + /** + * Default Constructor. + */ + public FileExistsException() { + super(); + } + + /** + * Construct an instance with the specified message. + * + * @param message The error message + */ + public FileExistsException(String message) { + super(message); + } + + /** + * Construct an instance with the specified file. + * + * @param file The file that exists + */ + public FileExistsException(File file) { + super("File " + file + " exists"); + } + + } +} diff --git a/app/src/main/java/com/litesuits/common/io/FilenameUtils.java b/app/src/main/java/com/litesuits/common/io/FilenameUtils.java index 63f878f..e936e5f 100644 --- a/app/src/main/java/com/litesuits/common/io/FilenameUtils.java +++ b/app/src/main/java/com/litesuits/common/io/FilenameUtils.java @@ -1,1063 +1,1063 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collection; - -/** - * General filename and filepath manipulation utilities. - *

    - * When dealing with filenames you can hit problems when moving from a Windows - * based development machine to a Unix based production machine. - * This class aims to help avoid those problems. - *

    - * NOTE: You may be able to avoid using this class entirely simply by - * using JDK {@link java.io.File File} objects and the two argument constructor - * {@link java.io.File#File(java.io.File, String) File(File,String)}. - *

    - * Most methods on this class are designed to work the same on both Unix and Windows. - * Those that don't include 'System', 'Unix' or 'Windows' in their name. - *

    - * Most methods recognise both separators (forward and back), and both - * sets of prefixes. See the javadoc of each method for details. - *

    - * This class defines six components within a filename - * (example C:\dev\project\file.txt): - *

      - *
    • the prefix - C:\
    • - *
    • the path - dev\project\
    • - *
    • the full path - C:\dev\project\
    • - *
    • the name - file.txt
    • - *
    • the base name - file
    • - *
    • the extension - txt
    • - *
    - * Note that this class works best if directory filenames end with a separator. - * If you omit the last separator, it is impossible to determine if the filename - * corresponds to a file or a directory. As a result, we have chosen to say - * it corresponds to a file. - *

    - * This class only supports Unix and Windows style names. - * Prefixes are matched as follows: - *

    - * Windows:
    - * a\b\c.txt           --> ""          --> relative
    - * \a\b\c.txt          --> "\"         --> current drive absolute
    - * C:a\b\c.txt         --> "C:"        --> drive relative
    - * C:\a\b\c.txt        --> "C:\"       --> absolute
    - * \\server\a\b\c.txt  --> "\\server\" --> UNC
    - *
    - * Unix:
    - * a/b/c.txt           --> ""          --> relative
    - * /a/b/c.txt          --> "/"         --> absolute
    - * ~/a/b/c.txt         --> "~/"        --> current user
    - * ~                   --> "~/"        --> current user (slash added)
    - * ~user/a/b/c.txt     --> "~user/"    --> named user
    - * ~user               --> "~user/"    --> named user (slash added)
    - * 
    - * Both prefix styles are matched always, irrespective of the machine that you are - * currently running on. - *

    - * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. - * - * @version $Id: FilenameUtils.java 1307462 2012-03-30 15:13:11Z ggregory $ - * @since 1.1 - */ -public class FilenameUtils { - - /** - * The extension separator character. - * @since 1.4 - */ - public static final char EXTENSION_SEPARATOR = '.'; - - /** - * The extension separator String. - * @since 1.4 - */ - public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); - - /** - * The Unix separator character. - */ - private static final char UNIX_SEPARATOR = '/'; - - /** - * The Windows separator character. - */ - private static final char WINDOWS_SEPARATOR = '\\'; - - /** - * The system separator character. - */ - private static final char SYSTEM_SEPARATOR = File.separatorChar; - - /** - * The separator character that is the opposite of the system separator. - */ - private static final char OTHER_SEPARATOR; - static { - if (isSystemWindows()) { - OTHER_SEPARATOR = UNIX_SEPARATOR; - } else { - OTHER_SEPARATOR = WINDOWS_SEPARATOR; - } - } - - /** - * Instances should NOT be constructed in standard programming. - */ - public FilenameUtils() { - super(); - } - - //----------------------------------------------------------------------- - /** - * Determines if Windows file system is in use. - * - * @return true if the system is Windows - */ - static boolean isSystemWindows() { - return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR; - } - - //----------------------------------------------------------------------- - /** - * Checks if the character is a separator. - * - * @param ch the character to check - * @return true if it is a separator character - */ - private static boolean isSeparator(char ch) { - return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; - } - - //----------------------------------------------------------------------- - /** - * Normalizes a path, removing double and single dot path steps. - *

    - * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format of the system. - *

    - * A trailing slash will be retained. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

    - * The output will be the same on both Unix and Windows except - * for the separator character. - *

    -     * /foo//               -->   /foo/
    -     * /foo/./              -->   /foo/
    -     * /foo/../bar          -->   /bar
    -     * /foo/../bar/         -->   /bar/
    -     * /foo/../bar/../baz   -->   /baz
    -     * //foo//./bar         -->   /foo/bar
    -     * /../                 -->   null
    -     * ../foo               -->   null
    -     * foo/bar/..           -->   foo/
    -     * foo/../../bar        -->   null
    -     * foo/../bar           -->   bar
    -     * //server/foo/../bar  -->   //server/bar
    -     * //server/../bar      -->   null
    -     * C:\foo\..\bar        -->   C:\bar
    -     * C:\..\bar            -->   null
    -     * ~/foo/../bar/        -->   ~/bar/
    -     * ~/../bar             -->   null
    -     * 
    - * (Note the file separator returned will be correct for Windows/Unix) - * - * @param filename the filename to normalize, null returns null - * @return the normalized filename, or null if invalid - */ - public static String normalize(String filename) { - return doNormalize(filename, SYSTEM_SEPARATOR, true); - } - /** - * Normalizes a path, removing double and single dot path steps. - *

    - * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format specified. - *

    - * A trailing slash will be retained. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

    - * The output will be the same on both Unix and Windows except - * for the separator character. - *

    -     * /foo//               -->   /foo/
    -     * /foo/./              -->   /foo/
    -     * /foo/../bar          -->   /bar
    -     * /foo/../bar/         -->   /bar/
    -     * /foo/../bar/../baz   -->   /baz
    -     * //foo//./bar         -->   /foo/bar
    -     * /../                 -->   null
    -     * ../foo               -->   null
    -     * foo/bar/..           -->   foo/
    -     * foo/../../bar        -->   null
    -     * foo/../bar           -->   bar
    -     * //server/foo/../bar  -->   //server/bar
    -     * //server/../bar      -->   null
    -     * C:\foo\..\bar        -->   C:\bar
    -     * C:\..\bar            -->   null
    -     * ~/foo/../bar/        -->   ~/bar/
    -     * ~/../bar             -->   null
    -     * 
    - * The output will be the same on both Unix and Windows including - * the separator character. - * - * @param filename the filename to normalize, null returns null - * @param unixSeparator {@code true} if a unix separator should - * be used or {@code false} if a windows separator should be used. - * @return the normalized filename, or null if invalid - * @since 2.0 - */ - public static String normalize(String filename, boolean unixSeparator) { - char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; - return doNormalize(filename, separator, true); - } - - //----------------------------------------------------------------------- - /** - * Normalizes a path, removing double and single dot path steps, - * and removing any final directory separator. - *

    - * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format of the system. - *

    - * A trailing slash will be removed. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

    - * The output will be the same on both Unix and Windows except - * for the separator character. - *

    -     * /foo//               -->   /foo
    -     * /foo/./              -->   /foo
    -     * /foo/../bar          -->   /bar
    -     * /foo/../bar/         -->   /bar
    -     * /foo/../bar/../baz   -->   /baz
    -     * //foo//./bar         -->   /foo/bar
    -     * /../                 -->   null
    -     * ../foo               -->   null
    -     * foo/bar/..           -->   foo
    -     * foo/../../bar        -->   null
    -     * foo/../bar           -->   bar
    -     * //server/foo/../bar  -->   //server/bar
    -     * //server/../bar      -->   null
    -     * C:\foo\..\bar        -->   C:\bar
    -     * C:\..\bar            -->   null
    -     * ~/foo/../bar/        -->   ~/bar
    -     * ~/../bar             -->   null
    -     * 
    - * (Note the file separator returned will be correct for Windows/Unix) - * - * @param filename the filename to normalize, null returns null - * @return the normalized filename, or null if invalid - */ - public static String normalizeNoEndSeparator(String filename) { - return doNormalize(filename, SYSTEM_SEPARATOR, false); - } - - /** - * Normalizes a path, removing double and single dot path steps, - * and removing any final directory separator. - *

    - * This method normalizes a path to a standard format. - * The input may contain separators in either Unix or Windows format. - * The output will contain separators in the format specified. - *

    - * A trailing slash will be removed. - * A double slash will be merged to a single slash (but UNC names are handled). - * A single dot path segment will be removed. - * A double dot will cause that path segment and the one before to be removed. - * If the double dot has no parent path segment to work with, {@code null} - * is returned. - *

    - * The output will be the same on both Unix and Windows including - * the separator character. - *

    -     * /foo//               -->   /foo
    -     * /foo/./              -->   /foo
    -     * /foo/../bar          -->   /bar
    -     * /foo/../bar/         -->   /bar
    -     * /foo/../bar/../baz   -->   /baz
    -     * //foo//./bar         -->   /foo/bar
    -     * /../                 -->   null
    -     * ../foo               -->   null
    -     * foo/bar/..           -->   foo
    -     * foo/../../bar        -->   null
    -     * foo/../bar           -->   bar
    -     * //server/foo/../bar  -->   //server/bar
    -     * //server/../bar      -->   null
    -     * C:\foo\..\bar        -->   C:\bar
    -     * C:\..\bar            -->   null
    -     * ~/foo/../bar/        -->   ~/bar
    -     * ~/../bar             -->   null
    -     * 
    - * - * @param filename the filename to normalize, null returns null - * @param unixSeparator {@code true} if a unix separator should - * be used or {@code false} if a windows separtor should be used. - * @return the normalized filename, or null if invalid - * @since 2.0 - */ - public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) { - char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; - return doNormalize(filename, separator, false); - } - - /** - * Internal method to perform the normalization. - * - * @param filename the filename - * @param separator The separator character to use - * @param keepSeparator true to keep the final separator - * @return the normalized filename - */ - private static String doNormalize(String filename, char separator, boolean keepSeparator) { - if (filename == null) { - return null; - } - int size = filename.length(); - if (size == 0) { - return filename; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - - char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy - filename.getChars(0, filename.length(), array, 0); - - // fix separators throughout - char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; - for (int i = 0; i < array.length; i++) { - if (array[i] == otherSeparator) { - array[i] = separator; - } - } - - // add extra separator on the end to simplify code below - boolean lastIsDirectory = true; - if (array[size - 1] != separator) { - array[size++] = separator; - lastIsDirectory = false; - } - - // adjoining slashes - for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == separator) { - System.arraycopy(array, i, array, i - 1, size - i); - size--; - i--; - } - } - - // dot slash - for (int i = prefix + 1; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && - (i == prefix + 1 || array[i - 2] == separator)) { - if (i == size - 1) { - lastIsDirectory = true; - } - System.arraycopy(array, i + 1, array, i - 1, size - i); - size -=2; - i--; - } - } - - // double dot slash - outer: - for (int i = prefix + 2; i < size; i++) { - if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && - (i == prefix + 2 || array[i - 3] == separator)) { - if (i == prefix + 2) { - return null; - } - if (i == size - 1) { - lastIsDirectory = true; - } - int j; - for (j = i - 4 ; j >= prefix; j--) { - if (array[j] == separator) { - // remove b/../ from a/b/../c - System.arraycopy(array, i + 1, array, j + 1, size - i); - size -= i - j; - i = j + 1; - continue outer; - } - } - // remove a/../ from a/../c - System.arraycopy(array, i + 1, array, prefix, size - i); - size -= i + 1 - prefix; - i = prefix + 1; - } - } - - if (size <= 0) { // should never be less than 0 - return ""; - } - if (size <= prefix) { // should never be less than prefix - return new String(array, 0, size); - } - if (lastIsDirectory && keepSeparator) { - return new String(array, 0, size); // keep trailing separator - } - return new String(array, 0, size - 1); // lose trailing separator - } - - //----------------------------------------------------------------------- - /** - * Concatenates a filename to a base path using normal command line style rules. - *

    - * The effect is equivalent to resultant directory after changing - * directory to the first argument, followed by changing directory to - * the second argument. - *

    - * The first argument is the base path, the second is the path to concatenate. - * The returned path is always normalized via {@link #normalize(String)}, - * thus .. is handled. - *

    - * If pathToAdd is absolute (has an absolute prefix), then - * it will be normalized and returned. - * Otherwise, the paths will be joined, normalized and returned. - *

    - * The output will be the same on both Unix and Windows except - * for the separator character. - *

    -     * /foo/ + bar          -->   /foo/bar
    -     * /foo + bar           -->   /foo/bar
    -     * /foo + /bar          -->   /bar
    -     * /foo + C:/bar        -->   C:/bar
    -     * /foo + C:bar         -->   C:bar (*)
    -     * /foo/a/ + ../bar     -->   foo/bar
    -     * /foo/ + ../../bar    -->   null
    -     * /foo/ + /bar         -->   /bar
    -     * /foo/.. + /bar       -->   /bar
    -     * /foo + bar/c.txt     -->   /foo/bar/c.txt
    -     * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)
    -     * 
    - * (*) Note that the Windows relative drive prefix is unreliable when - * used with this method. - * (!) Note that the first parameter must be a path. If it ends with a name, then - * the name will be built into the concatenated path. If this might be a problem, - * use {@link #getFullPath(String)} on the base path argument. - * - * @param basePath the base path to attach to, always treated as a path - * @param fullFilenameToAdd the filename (or path) to attach to the base - * @return the concatenated path, or null if invalid - */ - public static String concat(String basePath, String fullFilenameToAdd) { - int prefix = getPrefixLength(fullFilenameToAdd); - if (prefix < 0) { - return null; - } - if (prefix > 0) { - return normalize(fullFilenameToAdd); - } - if (basePath == null) { - return null; - } - int len = basePath.length(); - if (len == 0) { - return normalize(fullFilenameToAdd); - } - char ch = basePath.charAt(len - 1); - if (isSeparator(ch)) { - return normalize(basePath + fullFilenameToAdd); - } else { - return normalize(basePath + '/' + fullFilenameToAdd); - } - } - - //----------------------------------------------------------------------- - /** - * Converts all separators to the Unix separator of forward slash. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToUnix(String path) { - if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) { - return path; - } - return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); - } - - /** - * Converts all separators to the Windows separator of backslash. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToWindows(String path) { - if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) { - return path; - } - return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR); - } - - /** - * Converts all separators to the system separator. - * - * @param path the path to be changed, null ignored - * @return the updated path - */ - public static String separatorsToSystem(String path) { - if (path == null) { - return null; - } - if (isSystemWindows()) { - return separatorsToWindows(path); - } else { - return separatorsToUnix(path); - } - } - - //----------------------------------------------------------------------- - /** - * Returns the length of the filename prefix, such as C:/ or ~/. - *

    - * This method will handle a file in either Unix or Windows format. - *

    - * The prefix length includes the first slash in the full filename - * if applicable. Thus, it is possible that the length returned is greater - * than the length of the input string. - *

    -     * Windows:
    -     * a\b\c.txt           --> ""          --> relative
    -     * \a\b\c.txt          --> "\"         --> current drive absolute
    -     * C:a\b\c.txt         --> "C:"        --> drive relative
    -     * C:\a\b\c.txt        --> "C:\"       --> absolute
    -     * \\server\a\b\c.txt  --> "\\server\" --> UNC
    -     *
    -     * Unix:
    -     * a/b/c.txt           --> ""          --> relative
    -     * /a/b/c.txt          --> "/"         --> absolute
    -     * ~/a/b/c.txt         --> "~/"        --> current user
    -     * ~                   --> "~/"        --> current user (slash added)
    -     * ~user/a/b/c.txt     --> "~user/"    --> named user
    -     * ~user               --> "~user/"    --> named user (slash added)
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - * ie. both Unix and Windows prefixes are matched regardless. - * - * @param filename the filename to find the prefix in, null returns -1 - * @return the length of the prefix, -1 if invalid or null - */ - public static int getPrefixLength(String filename) { - if (filename == null) { - return -1; - } - int len = filename.length(); - if (len == 0) { - return 0; - } - char ch0 = filename.charAt(0); - if (ch0 == ':') { - return -1; - } - if (len == 1) { - if (ch0 == '~') { - return 2; // return a length greater than the input - } - return isSeparator(ch0) ? 1 : 0; - } else { - if (ch0 == '~') { - int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); - int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); - if (posUnix == -1 && posWin == -1) { - return len + 1; // return a length greater than the input - } - posUnix = posUnix == -1 ? posWin : posUnix; - posWin = posWin == -1 ? posUnix : posWin; - return Math.min(posUnix, posWin) + 1; - } - char ch1 = filename.charAt(1); - if (ch1 == ':') { - ch0 = Character.toUpperCase(ch0); - if (ch0 >= 'A' && ch0 <= 'Z') { - if (len == 2 || isSeparator(filename.charAt(2)) == false) { - return 2; - } - return 3; - } - return -1; - - } else if (isSeparator(ch0) && isSeparator(ch1)) { - int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); - int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); - if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) { - return -1; - } - posUnix = posUnix == -1 ? posWin : posUnix; - posWin = posWin == -1 ? posUnix : posWin; - return Math.min(posUnix, posWin) + 1; - } else { - return isSeparator(ch0) ? 1 : 0; - } - } - } - - /** - * Returns the index of the last directory separator character. - *

    - * This method will handle a file in either Unix or Windows format. - * The position of the last forward or backslash is returned. - *

    - * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to find the last path separator in, null returns -1 - * @return the index of the last separator character, or -1 if there - * is no such character - */ - public static int indexOfLastSeparator(String filename) { - if (filename == null) { - return -1; - } - int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); - int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); - return Math.max(lastUnixPos, lastWindowsPos); - } - - /** - * Returns the index of the last extension separator character, which is a dot. - *

    - * This method also checks that there is no directory separator after the last dot. - * To do this it uses {@link #indexOfLastSeparator(String)} which will - * handle a file in either Unix or Windows format. - *

    - * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to find the last path separator in, null returns -1 - * @return the index of the last separator character, or -1 if there - * is no such character - */ - public static int indexOfExtension(String filename) { - if (filename == null) { - return -1; - } - int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); - int lastSeparator = indexOfLastSeparator(filename); - return lastSeparator > extensionPos ? -1 : extensionPos; - } - - //----------------------------------------------------------------------- - /** - * Gets the prefix from a full filename, such as C:/ - * or ~/. - *

    - * This method will handle a file in either Unix or Windows format. - * The prefix includes the first slash in the full filename where applicable. - *

    -     * Windows:
    -     * a\b\c.txt           --> ""          --> relative
    -     * \a\b\c.txt          --> "\"         --> current drive absolute
    -     * C:a\b\c.txt         --> "C:"        --> drive relative
    -     * C:\a\b\c.txt        --> "C:\"       --> absolute
    -     * \\server\a\b\c.txt  --> "\\server\" --> UNC
    -     *
    -     * Unix:
    -     * a/b/c.txt           --> ""          --> relative
    -     * /a/b/c.txt          --> "/"         --> absolute
    -     * ~/a/b/c.txt         --> "~/"        --> current user
    -     * ~                   --> "~/"        --> current user (slash added)
    -     * ~user/a/b/c.txt     --> "~user/"    --> named user
    -     * ~user               --> "~user/"    --> named user (slash added)
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - * ie. both Unix and Windows prefixes are matched regardless. - * - * @param filename the filename to query, null returns null - * @return the prefix of the file, null if invalid - */ - public static String getPrefix(String filename) { - if (filename == null) { - return null; - } - int len = getPrefixLength(filename); - if (len < 0) { - return null; - } - if (len > filename.length()) { - return filename + UNIX_SEPARATOR; // we know this only happens for unix - } - return filename.substring(0, len); - } - - /** - * Gets the path from a full filename, which excludes the prefix. - *

    - * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before and - * including the last forward or backslash. - *

    -     * C:\a\b\c.txt --> a\b\
    -     * ~/a/b/c.txt  --> a/b/
    -     * a.txt        --> ""
    -     * a/b/c        --> a/b/
    -     * a/b/c/       --> a/b/c/
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - *

    - * This method drops the prefix from the result. - * See {@link #getFullPath(String)} for the method that retains the prefix. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getPath(String filename) { - return doGetPath(filename, 1); - } - - /** - * Gets the path from a full filename, which excludes the prefix, and - * also excluding the final directory separator. - *

    - * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before the - * last forward or backslash. - *

    -     * C:\a\b\c.txt --> a\b
    -     * ~/a/b/c.txt  --> a/b
    -     * a.txt        --> ""
    -     * a/b/c        --> a/b
    -     * a/b/c/       --> a/b/c
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - *

    - * This method drops the prefix from the result. - * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getPathNoEndSeparator(String filename) { - return doGetPath(filename, 0); - } - - /** - * Does the work of getting the path. - * - * @param filename the filename - * @param separatorAdd 0 to omit the end separator, 1 to return it - * @return the path - */ - private static String doGetPath(String filename, int separatorAdd) { - if (filename == null) { - return null; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - int index = indexOfLastSeparator(filename); - int endIndex = index+separatorAdd; - if (prefix >= filename.length() || index < 0 || prefix >= endIndex) { - return ""; - } - return filename.substring(prefix, endIndex); - } - - /** - * Gets the full path from a full filename, which is the prefix + path. - *

    - * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before and - * including the last forward or backslash. - *

    -     * C:\a\b\c.txt --> C:\a\b\
    -     * ~/a/b/c.txt  --> ~/a/b/
    -     * a.txt        --> ""
    -     * a/b/c        --> a/b/
    -     * a/b/c/       --> a/b/c/
    -     * C:           --> C:
    -     * C:\          --> C:\
    -     * ~            --> ~/
    -     * ~/           --> ~/
    -     * ~user        --> ~user/
    -     * ~user/       --> ~user/
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getFullPath(String filename) { - return doGetFullPath(filename, true); - } - - /** - * Gets the full path from a full filename, which is the prefix + path, - * and also excluding the final directory separator. - *

    - * This method will handle a file in either Unix or Windows format. - * The method is entirely text based, and returns the text before the - * last forward or backslash. - *

    -     * C:\a\b\c.txt --> C:\a\b
    -     * ~/a/b/c.txt  --> ~/a/b
    -     * a.txt        --> ""
    -     * a/b/c        --> a/b
    -     * a/b/c/       --> a/b/c
    -     * C:           --> C:
    -     * C:\          --> C:\
    -     * ~            --> ~
    -     * ~/           --> ~
    -     * ~user        --> ~user
    -     * ~user/       --> ~user
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the path of the file, an empty string if none exists, null if invalid - */ - public static String getFullPathNoEndSeparator(String filename) { - return doGetFullPath(filename, false); - } - - /** - * Does the work of getting the path. - * - * @param filename the filename - * @param includeSeparator true to include the end separator - * @return the path - */ - private static String doGetFullPath(String filename, boolean includeSeparator) { - if (filename == null) { - return null; - } - int prefix = getPrefixLength(filename); - if (prefix < 0) { - return null; - } - if (prefix >= filename.length()) { - if (includeSeparator) { - return getPrefix(filename); // add end slash if necessary - } else { - return filename; - } - } - int index = indexOfLastSeparator(filename); - if (index < 0) { - return filename.substring(0, prefix); - } - int end = index + (includeSeparator ? 1 : 0); - if (end == 0) { - end++; - } - return filename.substring(0, end); - } - - /** - * Gets the name minus the path from a full filename. - *

    - * This method will handle a file in either Unix or Windows format. - * The text after the last forward or backslash is returned. - *

    -     * a/b/c.txt --> c.txt
    -     * a.txt     --> a.txt
    -     * a/b/c     --> c
    -     * a/b/c/    --> ""
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to query, null returns null - * @return the name of the file without the path, or an empty string if none exists - */ - public static String getName(String filename) { - if (filename == null) { - return null; - } - int index = indexOfLastSeparator(filename); - return filename.substring(index + 1); - } - - /** - * Gets the extension of a filename. - *

    - * This method returns the textual part of the filename after the last dot. - * There must be no directory separator after the dot. - *

    -     * foo.txt      --> "txt"
    -     * a/b/c.jpg    --> "jpg"
    -     * a/b.txt/c    --> ""
    -     * a/b/c        --> ""
    -     * 
    - *

    - * The output will be the same irrespective of the machine that the code is running on. - * - * @param filename the filename to retrieve the extension of. - * @return the extension of the file or an empty string if none exists or {@code null} - * if the filename is {@code null}. - */ - public static String getExtension(String filename) { - if (filename == null) { - return null; - } - int index = indexOfExtension(filename); - if (index == -1) { - return ""; - } else { - return filename.substring(index + 1); - } - } - - //----------------------------------------------------------------------- - /** - * Checks whether the extension of the filename is that specified. - *

    - * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extension the extension to check for, null or empty checks for no extension - * @return true if the filename has the specified extension - */ - public static boolean isExtension(String filename, String extension) { - if (filename == null) { - return false; - } - if (extension == null || extension.length() == 0) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - return fileExt.equals(extension); - } - - /** - * Checks whether the extension of the filename is one of those specified. - *

    - * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the filename is one of the extensions - */ - public static boolean isExtension(String filename, String[] extensions) { - if (filename == null) { - return false; - } - if (extensions == null || extensions.length == 0) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - for (String extension : extensions) { - if (fileExt.equals(extension)) { - return true; - } - } - return false; - } - - /** - * Checks whether the extension of the filename is one of those specified. - *

    - * This method obtains the extension as the textual part of the filename - * after the last dot. There must be no directory separator after the dot. - * The extension check is case-sensitive on all platforms. - * - * @param filename the filename to query, null returns false - * @param extensions the extensions to check for, null checks for no extension - * @return true if the filename is one of the extensions - */ - public static boolean isExtension(String filename, Collection extensions) { - if (filename == null) { - return false; - } - if (extensions == null || extensions.isEmpty()) { - return indexOfExtension(filename) == -1; - } - String fileExt = getExtension(filename); - for (String extension : extensions) { - if (fileExt.equals(extension)) { - return true; - } - } - return false; - } - - //----------------------------------------------------------------------- - - /** - * Splits a string into a number of tokens. - * The text is split by '?' and '*'. - * Where multiple '*' occur consecutively they are collapsed into a single '*'. - * - * @param text the text to split - * @return the array of tokens, never null - */ - static String[] splitOnTokens(String text) { - // used by wildcardMatch - // package level so a unit test may run on this - - if (text.indexOf('?') == -1 && text.indexOf('*') == -1) { - return new String[] { text }; - } - - char[] array = text.toCharArray(); - ArrayList list = new ArrayList(); - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < array.length; i++) { - if (array[i] == '?' || array[i] == '*') { - if (buffer.length() != 0) { - list.add(buffer.toString()); - buffer.setLength(0); - } - if (array[i] == '?') { - list.add("?"); - } else if (list.isEmpty() || - i > 0 && list.get(list.size() - 1).equals("*") == false) { - list.add("*"); - } - } else { - buffer.append(array[i]); - } - } - if (buffer.length() != 0) { - list.add(buffer.toString()); - } - - return list.toArray( new String[ list.size() ] ); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collection; + +/** + * General filename and filepath manipulation utilities. + *

    + * When dealing with filenames you can hit problems when moving from a Windows + * based development machine to a Unix based production machine. + * This class aims to help avoid those problems. + *

    + * NOTE: You may be able to avoid using this class entirely simply by + * using JDK {@link java.io.File File} objects and the two argument constructor + * {@link java.io.File#File(java.io.File, String) File(File,String)}. + *

    + * Most methods on this class are designed to work the same on both Unix and Windows. + * Those that don't include 'System', 'Unix' or 'Windows' in their name. + *

    + * Most methods recognise both separators (forward and back), and both + * sets of prefixes. See the javadoc of each method for details. + *

    + * This class defines six components within a filename + * (example C:\dev\project\file.txt): + *

      + *
    • the prefix - C:\
    • + *
    • the path - dev\project\
    • + *
    • the full path - C:\dev\project\
    • + *
    • the name - file.txt
    • + *
    • the base name - file
    • + *
    • the extension - txt
    • + *
    + * Note that this class works best if directory filenames end with a separator. + * If you omit the last separator, it is impossible to determine if the filename + * corresponds to a file or a directory. As a result, we have chosen to say + * it corresponds to a file. + *

    + * This class only supports Unix and Windows style names. + * Prefixes are matched as follows: + *

    + * Windows:
    + * a\b\c.txt           --> ""          --> relative
    + * \a\b\c.txt          --> "\"         --> current drive absolute
    + * C:a\b\c.txt         --> "C:"        --> drive relative
    + * C:\a\b\c.txt        --> "C:\"       --> absolute
    + * \\server\a\b\c.txt  --> "\\server\" --> UNC
    + *
    + * Unix:
    + * a/b/c.txt           --> ""          --> relative
    + * /a/b/c.txt          --> "/"         --> absolute
    + * ~/a/b/c.txt         --> "~/"        --> current user
    + * ~                   --> "~/"        --> current user (slash added)
    + * ~user/a/b/c.txt     --> "~user/"    --> named user
    + * ~user               --> "~user/"    --> named user (slash added)
    + * 
    + * Both prefix styles are matched always, irrespective of the machine that you are + * currently running on. + *

    + * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils. + * + * @version $Id: FilenameUtils.java 1307462 2012-03-30 15:13:11Z ggregory $ + * @since 1.1 + */ +public class FilenameUtils { + + /** + * The extension separator character. + * @since 1.4 + */ + public static final char EXTENSION_SEPARATOR = '.'; + + /** + * The extension separator String. + * @since 1.4 + */ + public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR); + + /** + * The Unix separator character. + */ + private static final char UNIX_SEPARATOR = '/'; + + /** + * The Windows separator character. + */ + private static final char WINDOWS_SEPARATOR = '\\'; + + /** + * The system separator character. + */ + private static final char SYSTEM_SEPARATOR = File.separatorChar; + + /** + * The separator character that is the opposite of the system separator. + */ + private static final char OTHER_SEPARATOR; + static { + if (isSystemWindows()) { + OTHER_SEPARATOR = UNIX_SEPARATOR; + } else { + OTHER_SEPARATOR = WINDOWS_SEPARATOR; + } + } + + /** + * Instances should NOT be constructed in standard programming. + */ + public FilenameUtils() { + super(); + } + + //----------------------------------------------------------------------- + /** + * Determines if Windows file system is in use. + * + * @return true if the system is Windows + */ + static boolean isSystemWindows() { + return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR; + } + + //----------------------------------------------------------------------- + /** + * Checks if the character is a separator. + * + * @param ch the character to check + * @return true if it is a separator character + */ + private static boolean isSeparator(char ch) { + return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR; + } + + //----------------------------------------------------------------------- + /** + * Normalizes a path, removing double and single dot path steps. + *

    + * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

    + * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

    + * The output will be the same on both Unix and Windows except + * for the separator character. + *

    +     * /foo//               -->   /foo/
    +     * /foo/./              -->   /foo/
    +     * /foo/../bar          -->   /bar
    +     * /foo/../bar/         -->   /bar/
    +     * /foo/../bar/../baz   -->   /baz
    +     * //foo//./bar         -->   /foo/bar
    +     * /../                 -->   null
    +     * ../foo               -->   null
    +     * foo/bar/..           -->   foo/
    +     * foo/../../bar        -->   null
    +     * foo/../bar           -->   bar
    +     * //server/foo/../bar  -->   //server/bar
    +     * //server/../bar      -->   null
    +     * C:\foo\..\bar        -->   C:\bar
    +     * C:\..\bar            -->   null
    +     * ~/foo/../bar/        -->   ~/bar/
    +     * ~/../bar             -->   null
    +     * 
    + * (Note the file separator returned will be correct for Windows/Unix) + * + * @param filename the filename to normalize, null returns null + * @return the normalized filename, or null if invalid + */ + public static String normalize(String filename) { + return doNormalize(filename, SYSTEM_SEPARATOR, true); + } + /** + * Normalizes a path, removing double and single dot path steps. + *

    + * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format specified. + *

    + * A trailing slash will be retained. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

    + * The output will be the same on both Unix and Windows except + * for the separator character. + *

    +     * /foo//               -->   /foo/
    +     * /foo/./              -->   /foo/
    +     * /foo/../bar          -->   /bar
    +     * /foo/../bar/         -->   /bar/
    +     * /foo/../bar/../baz   -->   /baz
    +     * //foo//./bar         -->   /foo/bar
    +     * /../                 -->   null
    +     * ../foo               -->   null
    +     * foo/bar/..           -->   foo/
    +     * foo/../../bar        -->   null
    +     * foo/../bar           -->   bar
    +     * //server/foo/../bar  -->   //server/bar
    +     * //server/../bar      -->   null
    +     * C:\foo\..\bar        -->   C:\bar
    +     * C:\..\bar            -->   null
    +     * ~/foo/../bar/        -->   ~/bar/
    +     * ~/../bar             -->   null
    +     * 
    + * The output will be the same on both Unix and Windows including + * the separator character. + * + * @param filename the filename to normalize, null returns null + * @param unixSeparator {@code true} if a unix separator should + * be used or {@code false} if a windows separator should be used. + * @return the normalized filename, or null if invalid + * @since 2.0 + */ + public static String normalize(String filename, boolean unixSeparator) { + char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; + return doNormalize(filename, separator, true); + } + + //----------------------------------------------------------------------- + /** + * Normalizes a path, removing double and single dot path steps, + * and removing any final directory separator. + *

    + * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format of the system. + *

    + * A trailing slash will be removed. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

    + * The output will be the same on both Unix and Windows except + * for the separator character. + *

    +     * /foo//               -->   /foo
    +     * /foo/./              -->   /foo
    +     * /foo/../bar          -->   /bar
    +     * /foo/../bar/         -->   /bar
    +     * /foo/../bar/../baz   -->   /baz
    +     * //foo//./bar         -->   /foo/bar
    +     * /../                 -->   null
    +     * ../foo               -->   null
    +     * foo/bar/..           -->   foo
    +     * foo/../../bar        -->   null
    +     * foo/../bar           -->   bar
    +     * //server/foo/../bar  -->   //server/bar
    +     * //server/../bar      -->   null
    +     * C:\foo\..\bar        -->   C:\bar
    +     * C:\..\bar            -->   null
    +     * ~/foo/../bar/        -->   ~/bar
    +     * ~/../bar             -->   null
    +     * 
    + * (Note the file separator returned will be correct for Windows/Unix) + * + * @param filename the filename to normalize, null returns null + * @return the normalized filename, or null if invalid + */ + public static String normalizeNoEndSeparator(String filename) { + return doNormalize(filename, SYSTEM_SEPARATOR, false); + } + + /** + * Normalizes a path, removing double and single dot path steps, + * and removing any final directory separator. + *

    + * This method normalizes a path to a standard format. + * The input may contain separators in either Unix or Windows format. + * The output will contain separators in the format specified. + *

    + * A trailing slash will be removed. + * A double slash will be merged to a single slash (but UNC names are handled). + * A single dot path segment will be removed. + * A double dot will cause that path segment and the one before to be removed. + * If the double dot has no parent path segment to work with, {@code null} + * is returned. + *

    + * The output will be the same on both Unix and Windows including + * the separator character. + *

    +     * /foo//               -->   /foo
    +     * /foo/./              -->   /foo
    +     * /foo/../bar          -->   /bar
    +     * /foo/../bar/         -->   /bar
    +     * /foo/../bar/../baz   -->   /baz
    +     * //foo//./bar         -->   /foo/bar
    +     * /../                 -->   null
    +     * ../foo               -->   null
    +     * foo/bar/..           -->   foo
    +     * foo/../../bar        -->   null
    +     * foo/../bar           -->   bar
    +     * //server/foo/../bar  -->   //server/bar
    +     * //server/../bar      -->   null
    +     * C:\foo\..\bar        -->   C:\bar
    +     * C:\..\bar            -->   null
    +     * ~/foo/../bar/        -->   ~/bar
    +     * ~/../bar             -->   null
    +     * 
    + * + * @param filename the filename to normalize, null returns null + * @param unixSeparator {@code true} if a unix separator should + * be used or {@code false} if a windows separtor should be used. + * @return the normalized filename, or null if invalid + * @since 2.0 + */ + public static String normalizeNoEndSeparator(String filename, boolean unixSeparator) { + char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR; + return doNormalize(filename, separator, false); + } + + /** + * Internal method to perform the normalization. + * + * @param filename the filename + * @param separator The separator character to use + * @param keepSeparator true to keep the final separator + * @return the normalized filename + */ + private static String doNormalize(String filename, char separator, boolean keepSeparator) { + if (filename == null) { + return null; + } + int size = filename.length(); + if (size == 0) { + return filename; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + + char[] array = new char[size + 2]; // +1 for possible extra slash, +2 for arraycopy + filename.getChars(0, filename.length(), array, 0); + + // fix separators throughout + char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR; + for (int i = 0; i < array.length; i++) { + if (array[i] == otherSeparator) { + array[i] = separator; + } + } + + // add extra separator on the end to simplify code below + boolean lastIsDirectory = true; + if (array[size - 1] != separator) { + array[size++] = separator; + lastIsDirectory = false; + } + + // adjoining slashes + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == separator) { + System.arraycopy(array, i, array, i - 1, size - i); + size--; + i--; + } + } + + // dot slash + for (int i = prefix + 1; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && + (i == prefix + 1 || array[i - 2] == separator)) { + if (i == size - 1) { + lastIsDirectory = true; + } + System.arraycopy(array, i + 1, array, i - 1, size - i); + size -=2; + i--; + } + } + + // double dot slash + outer: + for (int i = prefix + 2; i < size; i++) { + if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' && + (i == prefix + 2 || array[i - 3] == separator)) { + if (i == prefix + 2) { + return null; + } + if (i == size - 1) { + lastIsDirectory = true; + } + int j; + for (j = i - 4 ; j >= prefix; j--) { + if (array[j] == separator) { + // remove b/../ from a/b/../c + System.arraycopy(array, i + 1, array, j + 1, size - i); + size -= i - j; + i = j + 1; + continue outer; + } + } + // remove a/../ from a/../c + System.arraycopy(array, i + 1, array, prefix, size - i); + size -= i + 1 - prefix; + i = prefix + 1; + } + } + + if (size <= 0) { // should never be less than 0 + return ""; + } + if (size <= prefix) { // should never be less than prefix + return new String(array, 0, size); + } + if (lastIsDirectory && keepSeparator) { + return new String(array, 0, size); // keep trailing separator + } + return new String(array, 0, size - 1); // lose trailing separator + } + + //----------------------------------------------------------------------- + /** + * Concatenates a filename to a base path using normal command line style rules. + *

    + * The effect is equivalent to resultant directory after changing + * directory to the first argument, followed by changing directory to + * the second argument. + *

    + * The first argument is the base path, the second is the path to concatenate. + * The returned path is always normalized via {@link #normalize(String)}, + * thus .. is handled. + *

    + * If pathToAdd is absolute (has an absolute prefix), then + * it will be normalized and returned. + * Otherwise, the paths will be joined, normalized and returned. + *

    + * The output will be the same on both Unix and Windows except + * for the separator character. + *

    +     * /foo/ + bar          -->   /foo/bar
    +     * /foo + bar           -->   /foo/bar
    +     * /foo + /bar          -->   /bar
    +     * /foo + C:/bar        -->   C:/bar
    +     * /foo + C:bar         -->   C:bar (*)
    +     * /foo/a/ + ../bar     -->   foo/bar
    +     * /foo/ + ../../bar    -->   null
    +     * /foo/ + /bar         -->   /bar
    +     * /foo/.. + /bar       -->   /bar
    +     * /foo + bar/c.txt     -->   /foo/bar/c.txt
    +     * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)
    +     * 
    + * (*) Note that the Windows relative drive prefix is unreliable when + * used with this method. + * (!) Note that the first parameter must be a path. If it ends with a name, then + * the name will be built into the concatenated path. If this might be a problem, + * use {@link #getFullPath(String)} on the base path argument. + * + * @param basePath the base path to attach to, always treated as a path + * @param fullFilenameToAdd the filename (or path) to attach to the base + * @return the concatenated path, or null if invalid + */ + public static String concat(String basePath, String fullFilenameToAdd) { + int prefix = getPrefixLength(fullFilenameToAdd); + if (prefix < 0) { + return null; + } + if (prefix > 0) { + return normalize(fullFilenameToAdd); + } + if (basePath == null) { + return null; + } + int len = basePath.length(); + if (len == 0) { + return normalize(fullFilenameToAdd); + } + char ch = basePath.charAt(len - 1); + if (isSeparator(ch)) { + return normalize(basePath + fullFilenameToAdd); + } else { + return normalize(basePath + '/' + fullFilenameToAdd); + } + } + + //----------------------------------------------------------------------- + /** + * Converts all separators to the Unix separator of forward slash. + * + * @param path the path to be changed, null ignored + * @return the updated path + */ + public static String separatorsToUnix(String path) { + if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) { + return path; + } + return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR); + } + + /** + * Converts all separators to the Windows separator of backslash. + * + * @param path the path to be changed, null ignored + * @return the updated path + */ + public static String separatorsToWindows(String path) { + if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) { + return path; + } + return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR); + } + + /** + * Converts all separators to the system separator. + * + * @param path the path to be changed, null ignored + * @return the updated path + */ + public static String separatorsToSystem(String path) { + if (path == null) { + return null; + } + if (isSystemWindows()) { + return separatorsToWindows(path); + } else { + return separatorsToUnix(path); + } + } + + //----------------------------------------------------------------------- + /** + * Returns the length of the filename prefix, such as C:/ or ~/. + *

    + * This method will handle a file in either Unix or Windows format. + *

    + * The prefix length includes the first slash in the full filename + * if applicable. Thus, it is possible that the length returned is greater + * than the length of the input string. + *

    +     * Windows:
    +     * a\b\c.txt           --> ""          --> relative
    +     * \a\b\c.txt          --> "\"         --> current drive absolute
    +     * C:a\b\c.txt         --> "C:"        --> drive relative
    +     * C:\a\b\c.txt        --> "C:\"       --> absolute
    +     * \\server\a\b\c.txt  --> "\\server\" --> UNC
    +     *
    +     * Unix:
    +     * a/b/c.txt           --> ""          --> relative
    +     * /a/b/c.txt          --> "/"         --> absolute
    +     * ~/a/b/c.txt         --> "~/"        --> current user
    +     * ~                   --> "~/"        --> current user (slash added)
    +     * ~user/a/b/c.txt     --> "~user/"    --> named user
    +     * ~user               --> "~user/"    --> named user (slash added)
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + * ie. both Unix and Windows prefixes are matched regardless. + * + * @param filename the filename to find the prefix in, null returns -1 + * @return the length of the prefix, -1 if invalid or null + */ + public static int getPrefixLength(String filename) { + if (filename == null) { + return -1; + } + int len = filename.length(); + if (len == 0) { + return 0; + } + char ch0 = filename.charAt(0); + if (ch0 == ':') { + return -1; + } + if (len == 1) { + if (ch0 == '~') { + return 2; // return a length greater than the input + } + return isSeparator(ch0) ? 1 : 0; + } else { + if (ch0 == '~') { + int posUnix = filename.indexOf(UNIX_SEPARATOR, 1); + int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1); + if (posUnix == -1 && posWin == -1) { + return len + 1; // return a length greater than the input + } + posUnix = posUnix == -1 ? posWin : posUnix; + posWin = posWin == -1 ? posUnix : posWin; + return Math.min(posUnix, posWin) + 1; + } + char ch1 = filename.charAt(1); + if (ch1 == ':') { + ch0 = Character.toUpperCase(ch0); + if (ch0 >= 'A' && ch0 <= 'Z') { + if (len == 2 || isSeparator(filename.charAt(2)) == false) { + return 2; + } + return 3; + } + return -1; + + } else if (isSeparator(ch0) && isSeparator(ch1)) { + int posUnix = filename.indexOf(UNIX_SEPARATOR, 2); + int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2); + if (posUnix == -1 && posWin == -1 || posUnix == 2 || posWin == 2) { + return -1; + } + posUnix = posUnix == -1 ? posWin : posUnix; + posWin = posWin == -1 ? posUnix : posWin; + return Math.min(posUnix, posWin) + 1; + } else { + return isSeparator(ch0) ? 1 : 0; + } + } + } + + /** + * Returns the index of the last directory separator character. + *

    + * This method will handle a file in either Unix or Windows format. + * The position of the last forward or backslash is returned. + *

    + * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to find the last path separator in, null returns -1 + * @return the index of the last separator character, or -1 if there + * is no such character + */ + public static int indexOfLastSeparator(String filename) { + if (filename == null) { + return -1; + } + int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR); + int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR); + return Math.max(lastUnixPos, lastWindowsPos); + } + + /** + * Returns the index of the last extension separator character, which is a dot. + *

    + * This method also checks that there is no directory separator after the last dot. + * To do this it uses {@link #indexOfLastSeparator(String)} which will + * handle a file in either Unix or Windows format. + *

    + * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to find the last path separator in, null returns -1 + * @return the index of the last separator character, or -1 if there + * is no such character + */ + public static int indexOfExtension(String filename) { + if (filename == null) { + return -1; + } + int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR); + int lastSeparator = indexOfLastSeparator(filename); + return lastSeparator > extensionPos ? -1 : extensionPos; + } + + //----------------------------------------------------------------------- + /** + * Gets the prefix from a full filename, such as C:/ + * or ~/. + *

    + * This method will handle a file in either Unix or Windows format. + * The prefix includes the first slash in the full filename where applicable. + *

    +     * Windows:
    +     * a\b\c.txt           --> ""          --> relative
    +     * \a\b\c.txt          --> "\"         --> current drive absolute
    +     * C:a\b\c.txt         --> "C:"        --> drive relative
    +     * C:\a\b\c.txt        --> "C:\"       --> absolute
    +     * \\server\a\b\c.txt  --> "\\server\" --> UNC
    +     *
    +     * Unix:
    +     * a/b/c.txt           --> ""          --> relative
    +     * /a/b/c.txt          --> "/"         --> absolute
    +     * ~/a/b/c.txt         --> "~/"        --> current user
    +     * ~                   --> "~/"        --> current user (slash added)
    +     * ~user/a/b/c.txt     --> "~user/"    --> named user
    +     * ~user               --> "~user/"    --> named user (slash added)
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + * ie. both Unix and Windows prefixes are matched regardless. + * + * @param filename the filename to query, null returns null + * @return the prefix of the file, null if invalid + */ + public static String getPrefix(String filename) { + if (filename == null) { + return null; + } + int len = getPrefixLength(filename); + if (len < 0) { + return null; + } + if (len > filename.length()) { + return filename + UNIX_SEPARATOR; // we know this only happens for unix + } + return filename.substring(0, len); + } + + /** + * Gets the path from a full filename, which excludes the prefix. + *

    + * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before and + * including the last forward or backslash. + *

    +     * C:\a\b\c.txt --> a\b\
    +     * ~/a/b/c.txt  --> a/b/
    +     * a.txt        --> ""
    +     * a/b/c        --> a/b/
    +     * a/b/c/       --> a/b/c/
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + *

    + * This method drops the prefix from the result. + * See {@link #getFullPath(String)} for the method that retains the prefix. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getPath(String filename) { + return doGetPath(filename, 1); + } + + /** + * Gets the path from a full filename, which excludes the prefix, and + * also excluding the final directory separator. + *

    + * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before the + * last forward or backslash. + *

    +     * C:\a\b\c.txt --> a\b
    +     * ~/a/b/c.txt  --> a/b
    +     * a.txt        --> ""
    +     * a/b/c        --> a/b
    +     * a/b/c/       --> a/b/c
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + *

    + * This method drops the prefix from the result. + * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getPathNoEndSeparator(String filename) { + return doGetPath(filename, 0); + } + + /** + * Does the work of getting the path. + * + * @param filename the filename + * @param separatorAdd 0 to omit the end separator, 1 to return it + * @return the path + */ + private static String doGetPath(String filename, int separatorAdd) { + if (filename == null) { + return null; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + int index = indexOfLastSeparator(filename); + int endIndex = index+separatorAdd; + if (prefix >= filename.length() || index < 0 || prefix >= endIndex) { + return ""; + } + return filename.substring(prefix, endIndex); + } + + /** + * Gets the full path from a full filename, which is the prefix + path. + *

    + * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before and + * including the last forward or backslash. + *

    +     * C:\a\b\c.txt --> C:\a\b\
    +     * ~/a/b/c.txt  --> ~/a/b/
    +     * a.txt        --> ""
    +     * a/b/c        --> a/b/
    +     * a/b/c/       --> a/b/c/
    +     * C:           --> C:
    +     * C:\          --> C:\
    +     * ~            --> ~/
    +     * ~/           --> ~/
    +     * ~user        --> ~user/
    +     * ~user/       --> ~user/
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getFullPath(String filename) { + return doGetFullPath(filename, true); + } + + /** + * Gets the full path from a full filename, which is the prefix + path, + * and also excluding the final directory separator. + *

    + * This method will handle a file in either Unix or Windows format. + * The method is entirely text based, and returns the text before the + * last forward or backslash. + *

    +     * C:\a\b\c.txt --> C:\a\b
    +     * ~/a/b/c.txt  --> ~/a/b
    +     * a.txt        --> ""
    +     * a/b/c        --> a/b
    +     * a/b/c/       --> a/b/c
    +     * C:           --> C:
    +     * C:\          --> C:\
    +     * ~            --> ~
    +     * ~/           --> ~
    +     * ~user        --> ~user
    +     * ~user/       --> ~user
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to query, null returns null + * @return the path of the file, an empty string if none exists, null if invalid + */ + public static String getFullPathNoEndSeparator(String filename) { + return doGetFullPath(filename, false); + } + + /** + * Does the work of getting the path. + * + * @param filename the filename + * @param includeSeparator true to include the end separator + * @return the path + */ + private static String doGetFullPath(String filename, boolean includeSeparator) { + if (filename == null) { + return null; + } + int prefix = getPrefixLength(filename); + if (prefix < 0) { + return null; + } + if (prefix >= filename.length()) { + if (includeSeparator) { + return getPrefix(filename); // add end slash if necessary + } else { + return filename; + } + } + int index = indexOfLastSeparator(filename); + if (index < 0) { + return filename.substring(0, prefix); + } + int end = index + (includeSeparator ? 1 : 0); + if (end == 0) { + end++; + } + return filename.substring(0, end); + } + + /** + * Gets the name minus the path from a full filename. + *

    + * This method will handle a file in either Unix or Windows format. + * The text after the last forward or backslash is returned. + *

    +     * a/b/c.txt --> c.txt
    +     * a.txt     --> a.txt
    +     * a/b/c     --> c
    +     * a/b/c/    --> ""
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to query, null returns null + * @return the name of the file without the path, or an empty string if none exists + */ + public static String getName(String filename) { + if (filename == null) { + return null; + } + int index = indexOfLastSeparator(filename); + return filename.substring(index + 1); + } + + /** + * Gets the extension of a filename. + *

    + * This method returns the textual part of the filename after the last dot. + * There must be no directory separator after the dot. + *

    +     * foo.txt      --> "txt"
    +     * a/b/c.jpg    --> "jpg"
    +     * a/b.txt/c    --> ""
    +     * a/b/c        --> ""
    +     * 
    + *

    + * The output will be the same irrespective of the machine that the code is running on. + * + * @param filename the filename to retrieve the extension of. + * @return the extension of the file or an empty string if none exists or {@code null} + * if the filename is {@code null}. + */ + public static String getExtension(String filename) { + if (filename == null) { + return null; + } + int index = indexOfExtension(filename); + if (index == -1) { + return ""; + } else { + return filename.substring(index + 1); + } + } + + //----------------------------------------------------------------------- + /** + * Checks whether the extension of the filename is that specified. + *

    + * This method obtains the extension as the textual part of the filename + * after the last dot. There must be no directory separator after the dot. + * The extension check is case-sensitive on all platforms. + * + * @param filename the filename to query, null returns false + * @param extension the extension to check for, null or empty checks for no extension + * @return true if the filename has the specified extension + */ + public static boolean isExtension(String filename, String extension) { + if (filename == null) { + return false; + } + if (extension == null || extension.length() == 0) { + return indexOfExtension(filename) == -1; + } + String fileExt = getExtension(filename); + return fileExt.equals(extension); + } + + /** + * Checks whether the extension of the filename is one of those specified. + *

    + * This method obtains the extension as the textual part of the filename + * after the last dot. There must be no directory separator after the dot. + * The extension check is case-sensitive on all platforms. + * + * @param filename the filename to query, null returns false + * @param extensions the extensions to check for, null checks for no extension + * @return true if the filename is one of the extensions + */ + public static boolean isExtension(String filename, String[] extensions) { + if (filename == null) { + return false; + } + if (extensions == null || extensions.length == 0) { + return indexOfExtension(filename) == -1; + } + String fileExt = getExtension(filename); + for (String extension : extensions) { + if (fileExt.equals(extension)) { + return true; + } + } + return false; + } + + /** + * Checks whether the extension of the filename is one of those specified. + *

    + * This method obtains the extension as the textual part of the filename + * after the last dot. There must be no directory separator after the dot. + * The extension check is case-sensitive on all platforms. + * + * @param filename the filename to query, null returns false + * @param extensions the extensions to check for, null checks for no extension + * @return true if the filename is one of the extensions + */ + public static boolean isExtension(String filename, Collection extensions) { + if (filename == null) { + return false; + } + if (extensions == null || extensions.isEmpty()) { + return indexOfExtension(filename) == -1; + } + String fileExt = getExtension(filename); + for (String extension : extensions) { + if (fileExt.equals(extension)) { + return true; + } + } + return false; + } + + //----------------------------------------------------------------------- + + /** + * Splits a string into a number of tokens. + * The text is split by '?' and '*'. + * Where multiple '*' occur consecutively they are collapsed into a single '*'. + * + * @param text the text to split + * @return the array of tokens, never null + */ + static String[] splitOnTokens(String text) { + // used by wildcardMatch + // package level so a unit test may run on this + + if (text.indexOf('?') == -1 && text.indexOf('*') == -1) { + return new String[] { text }; + } + + char[] array = text.toCharArray(); + ArrayList list = new ArrayList(); + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < array.length; i++) { + if (array[i] == '?' || array[i] == '*') { + if (buffer.length() != 0) { + list.add(buffer.toString()); + buffer.setLength(0); + } + if (array[i] == '?') { + list.add("?"); + } else if (list.isEmpty() || + i > 0 && list.get(list.size() - 1).equals("*") == false) { + list.add("*"); + } + } else { + buffer.append(array[i]); + } + } + if (buffer.length() != 0) { + list.add(buffer.toString()); + } + + return list.toArray( new String[ list.size() ] ); + } + +} diff --git a/app/src/main/java/com/litesuits/common/io/IOUtils.java b/app/src/main/java/com/litesuits/common/io/IOUtils.java index 5cd7fd0..30355c7 100644 --- a/app/src/main/java/com/litesuits/common/io/IOUtils.java +++ b/app/src/main/java/com/litesuits/common/io/IOUtils.java @@ -1,2409 +1,2409 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io; - -import android.os.Build; -import com.litesuits.common.io.stream.*; - -import java.io.*; -import java.net.*; -import java.nio.channels.Selector; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * General IO stream manipulation utilities. - *

    - * This class provides static utility methods for input/output operations. - *

      - *
    • closeQuietly - these methods close a stream ignoring nulls and exceptions - *
    • toXxx/read - these methods read data from a stream - *
    • write - these methods write data to a stream - *
    • copy - these methods copy all the data from one stream to another - *
    • contentEquals - these methods compare the content of two streams - *
    - *

    - * The byte-to-char methods and char-to-byte methods involve a conversion step. - * Two methods are provided in each case, one that uses the platform default - * encoding and the other which allows you to specify an encoding. You are - * encouraged to always specify an encoding because relying on the platform - * default can lead to unexpected results, for example when moving from - * development to production. - *

    - * All the methods in this class that read a stream are buffered internally. - * This means that there is no cause to use a BufferedInputStream - * or BufferedReader. The default buffer size of 4K has been shown - * to be efficient in tests. - *

    - * Wherever possible, the methods in this class do not flush or close - * the stream. This is to avoid making non-portable assumptions about the - * streams' origin and further use. Thus the caller is still responsible for - * closing streams after use. - *

    - * Origin of code: Excalibur. - * - * @version $Id: IOUtils.java 1326636 2012-04-16 14:54:53Z ggregory $ - */ -public class IOUtils { - // NOTE: This class is focussed on InputStream, OutputStream, Reader and - // Writer. Each method should take at least one of these as a parameter, - // or return one of them. - - private static final int EOF = -1; - /** - * The Unix directory separator character. - */ - public static final char DIR_SEPARATOR_UNIX = '/'; - /** - * The Windows directory separator character. - */ - public static final char DIR_SEPARATOR_WINDOWS = '\\'; - /** - * The system directory separator character. - */ - public static final char DIR_SEPARATOR = File.separatorChar; - /** - * The Unix line separator string. - */ - public static final String LINE_SEPARATOR_UNIX = "\n"; - /** - * The Windows line separator string. - */ - public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; - /** - * The system line separator string. - */ - public static final String LINE_SEPARATOR; - - static { - // avoid security issues - StringBuilderWriter buf = new StringBuilderWriter(4); - PrintWriter out = new PrintWriter(buf); - out.println(); - LINE_SEPARATOR = buf.toString(); - out.close(); - } - - /** - * The default buffer size ({@value}) to use for - * {@link #copyLarge(java.io.InputStream, java.io.OutputStream)} - * and - * {@link #copyLarge(java.io.Reader, java.io.Writer)} - */ - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - - /** - * The default buffer size to use for the skip() methods. - */ - private static final int SKIP_BUFFER_SIZE = 2048; - - // Allocated in the relevant skip method if necessary. - /* - * N.B. no need to synchronize these because: - * - we don't care if the buffer is created multiple times (the data is ignored) - * - we always use the same size buffer, so if it it is recreated it will still be OK - * (if the buffer size were variable, we would need to synch. to ensure some other thread - * did not create a smaller one) - */ - private static char[] SKIP_CHAR_BUFFER; - private static byte[] SKIP_BYTE_BUFFER; - - /** - * Instances should NOT be constructed in standard programming. - */ - public IOUtils() { - super(); - } - - //----------------------------------------------------------------------- - - /** - * Closes a URLConnection. - * - * @param conn the connection to close. - * @since 2.4 - */ - public static void close(URLConnection conn) { - if (conn instanceof HttpURLConnection) { - ((HttpURLConnection) conn).disconnect(); - } - } - - /** - * Unconditionally close an Reader. - *

    - * Equivalent to {@link java.io.Reader#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   char[] data = new char[1024];
    -     *   Reader in = null;
    -     *   try {
    -     *       in = new FileReader("foo.txt");
    -     *       in.read(data);
    -     *       in.close(); //close errors are handled
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(in);
    -     *   }
    -     * 
    - * - * @param input the Reader to close, may be null or already closed - */ - public static void closeQuietly(Reader input) { - closeQuietly((Closeable) input); - } - - /** - * Unconditionally close a Writer. - *

    - * Equivalent to {@link java.io.Writer#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   Writer out = null;
    -     *   try {
    -     *       out = new StringWriter();
    -     *       out.write("Hello World");
    -     *       out.close(); //close errors are handled
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(out);
    -     *   }
    -     * 
    - * - * @param output the Writer to close, may be null or already closed - */ - public static void closeQuietly(Writer output) { - closeQuietly((Closeable) output); - } - - /** - * Unconditionally close an InputStream. - *

    - * Equivalent to {@link java.io.InputStream#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   byte[] data = new byte[1024];
    -     *   InputStream in = null;
    -     *   try {
    -     *       in = new FileInputStream("foo.txt");
    -     *       in.read(data);
    -     *       in.close(); //close errors are handled
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(in);
    -     *   }
    -     * 
    - * - * @param input the InputStream to close, may be null or already closed - */ - public static void closeQuietly(InputStream input) { - closeQuietly((Closeable) input); - } - - /** - * Unconditionally close an OutputStream. - *

    - * Equivalent to {@link java.io.OutputStream#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     * byte[] data = "Hello, World".getBytes();
    -     *
    -     * OutputStream out = null;
    -     * try {
    -     *     out = new FileOutputStream("foo.txt");
    -     *     out.write(data);
    -     *     out.close(); //close errors are handled
    -     * } catch (IOException e) {
    -     *     // error handling
    -     * } finally {
    -     *     IOUtils.closeQuietly(out);
    -     * }
    -     * 
    - * - * @param output the OutputStream to close, may be null or already closed - */ - public static void closeQuietly(OutputStream output) { - closeQuietly((Closeable) output); - } - - /** - * Unconditionally close a Closeable. - *

    - * Equivalent to {@link java.io.Closeable#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   Closeable closeable = null;
    -     *   try {
    -     *       closeable = new FileReader("foo.txt");
    -     *       // process closeable
    -     *       closeable.close();
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(closeable);
    -     *   }
    -     * 
    - * - * @param closeable the object to close, may be null or already closed - * @since 2.0 - */ - public static void closeQuietly(Closeable closeable) { - try { - if (closeable != null) { - closeable.close(); - } - } catch (IOException ioe) { - // ignore - } - } - - /** - * Unconditionally close a Socket. - *

    - * Equivalent to {@link java.net.Socket#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   Socket socket = null;
    -     *   try {
    -     *       socket = new Socket("http://www.foo.com/", 80);
    -     *       // process socket
    -     *       socket.close();
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(socket);
    -     *   }
    -     * 
    - * - * @param sock the Socket to close, may be null or already closed - * @since 2.0 - */ - public static void closeQuietly(Socket sock) { - if (sock != null) { - try { - sock.close(); - } catch (IOException ioe) { - // ignored - } - } - } - - /** - * Unconditionally close a Selector. - *

    - * Equivalent to {@link java.nio.channels.Selector#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   Selector selector = null;
    -     *   try {
    -     *       selector = Selector.open();
    -     *       // process socket
    -     *
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(selector);
    -     *   }
    -     * 
    - * - * @param selector the Selector to close, may be null or already closed - * @since 2.2 - */ - public static void closeQuietly(Selector selector) { - if (selector != null) { - try { - selector.close(); - } catch (IOException ioe) { - // ignored - } - } - } - - /** - * Unconditionally close a ServerSocket. - *

    - * Equivalent to {@link java.net.ServerSocket#close()}, except any exceptions will be ignored. - * This is typically used in finally blocks. - *

    - * Example code: - *

    -     *   ServerSocket socket = null;
    -     *   try {
    -     *       socket = new ServerSocket();
    -     *       // process socket
    -     *       socket.close();
    -     *   } catch (Exception e) {
    -     *       // error handling
    -     *   } finally {
    -     *       IOUtils.closeQuietly(socket);
    -     *   }
    -     * 
    - * - * @param sock the ServerSocket to close, may be null or already closed - * @since 2.2 - */ - public static void closeQuietly(ServerSocket sock) { - if (sock != null) { - try { - sock.close(); - } catch (IOException ioe) { - // ignored - } - } - } - - /** - * Fetches entire contents of an InputStream and represent - * same data as result InputStream. - *

    - * This method is useful where, - *

      - *
    • Source InputStream is slow.
    • - *
    • It has network resources associated, so we cannot keep it open for - * long time.
    • - *
    • It has network timeout associated.
    • - *
    - * It can be used in favor of {@link #toByteArray(java.io.InputStream)}, since it - * avoids unnecessary allocation and copy of byte[].
    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input Stream to be fully buffered. - * @return A fully buffered stream. - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static InputStream toBufferedInputStream(InputStream input) throws IOException { - return com.litesuits.common.io.stream.ByteArrayOutputStream.toBufferedInputStream(input); - } - - /** - * Returns the given reader if it is a {@link java.io.BufferedReader}, otherwise creates a toBufferedReader for the given - * reader. - * - * @param reader the reader to wrap or return - * @return the given reader or a new {@link java.io.BufferedReader} for the given reader - * @since 2.2 - */ - public static BufferedReader toBufferedReader(Reader reader) { - return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); - } - - // read toByteArray - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a byte[]. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static byte[] toByteArray(InputStream input) throws IOException { - com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); - copy(input, output); - return output.toByteArray(); - } - - /** - * Get contents of an InputStream as a byte[]. - * Use this method instead of toByteArray(InputStream) - * when InputStream size is known. - * NOTE: the method checks that the length can safely be cast to an int without truncation - * before using {@link com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int)} to read into the byte array. - * (Arrays can have no more than Integer.MAX_VALUE entries anyway) - * - * @param input the InputStream to read from - * @param size the size of InputStream - * @return the requested byte array - * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size - * @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE - * @see com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int) - * @since 2.1 - */ - public static byte[] toByteArray(InputStream input, long size) throws IOException { - - if (size > Integer.MAX_VALUE) { - throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); - } - - return toByteArray(input, (int) size); - } - - /** - * Get the contents of an InputStream as a byte[]. - * Use this method instead of toByteArray(InputStream) - * when InputStream size is known - * - * @param input the InputStream to read from - * @param size the size of InputStream - * @return the requested byte array - * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size - * @throws IllegalArgumentException if size is less than zero - * @since 2.1 - */ - public static byte[] toByteArray(InputStream input, int size) throws IOException { - - if (size < 0) { - throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); - } - - if (size == 0) { - return new byte[0]; - } - - byte[] data = new byte[size]; - int offset = 0; - int readed; - - while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) { - offset += readed; - } - - if (offset != size) { - throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size); - } - - return data; - } - - /** - * Get the contents of a Reader as a byte[] - * using the default character encoding of the platform. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static byte[] toByteArray(Reader input) throws IOException { - return toByteArray(input, Charset.defaultCharset()); - } - - /** - * Get the contents of a Reader as a byte[] - * using the specified character encoding. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @param encoding the encoding to use, null means platform default - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static byte[] toByteArray(Reader input, Charset encoding) throws IOException { - com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); - copy(input, output, encoding); - return output.toByteArray(); - } - - /** - * Get the contents of a Reader as a byte[] - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @param encoding the encoding to use, null means platform default - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static byte[] toByteArray(Reader input, String encoding) throws IOException { - return toByteArray(input, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a String as a byte[] - * using the default character encoding of the platform. - *

    - * This is the same as {@link String#getBytes()}. - * - * @param input the String to convert - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs (never occurs) - * @deprecated Use {@link String#getBytes()} - */ - @Deprecated - public static byte[] toByteArray(String input) throws IOException { - return input.getBytes(); - } - - /** - * Get the contents of a URI as a byte[]. - * - * @param uri the URI to read - * @return the requested byte array - * @throws NullPointerException if the uri is null - * @throws java.io.IOException if an I/O exception occurs - * @since 2.4 - */ - public static byte[] toByteArray(URI uri) throws IOException { - return IOUtils.toByteArray(uri.toURL()); - } - - /** - * Get the contents of a URL as a byte[]. - * - * @param url the URL to read - * @return the requested byte array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O exception occurs - * @since 2.4 - */ - public static byte[] toByteArray(URL url) throws IOException { - URLConnection conn = url.openConnection(); - try { - return IOUtils.toByteArray(conn); - } finally { - close(conn); - } - } - - /** - * Get the contents of a URLConnection as a byte[]. - * - * @param urlConn the URLConnection to read - * @return the requested byte array - * @throws NullPointerException if the urlConn is null - * @throws java.io.IOException if an I/O exception occurs - * @since 2.4 - */ - public static byte[] toByteArray(URLConnection urlConn) throws IOException { - InputStream inputStream = urlConn.getInputStream(); - try { - return IOUtils.toByteArray(inputStream); - } finally { - inputStream.close(); - } - } - - // read char[] - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a character array - * using the default character encoding of the platform. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static char[] toCharArray(InputStream is) throws IOException { - return toCharArray(is, Charset.defaultCharset()); - } - - /** - * Get the contents of an InputStream as a character array - * using the specified character encoding. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static char[] toCharArray(InputStream is, Charset encoding) - throws IOException { - CharArrayWriter output = new CharArrayWriter(); - copy(is, output, encoding); - return output.toCharArray(); - } - - /** - * Get the contents of an InputStream as a character array - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param is the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static char[] toCharArray(InputStream is, String encoding) throws IOException { - return toCharArray(is, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a Reader as a character array. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested character array - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static char[] toCharArray(Reader input) throws IOException { - CharArrayWriter sw = new CharArrayWriter(); - copy(input, sw); - return sw.toCharArray(); - } - - // read toString - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a String - * using the default character encoding of the platform. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static String toString(InputStream input) throws IOException { - return toString(input, Charset.defaultCharset()); - } - - /** - * Get the contents of an InputStream as a String - * using the specified character encoding. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * - * @param input the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static String toString(InputStream input, Charset encoding) throws IOException { - StringBuilderWriter sw = new StringBuilderWriter(); - copy(input, sw, encoding); - return sw.toString(); - } - - /** - * Get the contents of an InputStream as a String - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - */ - public static String toString(InputStream input, String encoding) - throws IOException { - return toString(input, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a Reader as a String. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static String toString(Reader input) throws IOException { - StringBuilderWriter sw = new StringBuilderWriter(); - copy(input, sw); - return sw.toString(); - } - - /** - * Gets the contents at the given URI. - * - * @param uri The URI source. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.1 - */ - public static String toString(URI uri) throws IOException { - return toString(uri, Charset.defaultCharset()); - } - - /** - * Gets the contents at the given URI. - * - * @param uri The URI source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.3. - */ - public static String toString(URI uri, Charset encoding) throws IOException { - return toString(uri.toURL(), Charsets.toCharset(encoding)); - } - - /** - * Gets the contents at the given URI. - * - * @param uri The URI source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.1 - */ - public static String toString(URI uri, String encoding) throws IOException { - return toString(uri, Charsets.toCharset(encoding)); - } - - /** - * Gets the contents at the given URL. - * - * @param url The URL source. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.1 - */ - public static String toString(URL url) throws IOException { - return toString(url, Charset.defaultCharset()); - } - - /** - * Gets the contents at the given URL. - * - * @param url The URL source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @since 2.3 - */ - public static String toString(URL url, Charset encoding) throws IOException { - InputStream inputStream = url.openStream(); - try { - return toString(inputStream, encoding); - } finally { - inputStream.close(); - } - } - - /** - * Gets the contents at the given URL. - * - * @param url The URL source. - * @param encoding The encoding name for the URL contents. - * @return The contents of the URL as a String. - * @throws java.io.IOException if an I/O exception occurs. - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.1 - */ - public static String toString(URL url, String encoding) throws IOException { - return toString(url, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a byte[] as a String - * using the default character encoding of the platform. - * - * @param input the byte array to read from - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs (never occurs) - * @deprecated Use {@link String#String(byte[])} - */ - @Deprecated - public static String toString(byte[] input) throws IOException { - return new String(input); - } - - /** - * Get the contents of a byte[] as a String - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - * - * @param input the byte array to read from - * @param encoding the encoding to use, null means platform default - * @return the requested String - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs (never occurs) - */ - public static String toString(byte[] input, String encoding) throws IOException { - return new String(input, encoding); - } - - // readLines - //----------------------------------------------------------------------- - - /** - * Get the contents of an InputStream as a list of Strings, - * one entry per line, using the default character encoding of the platform. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static List readLines(InputStream input) throws IOException { - return readLines(input, Charset.defaultCharset()); - } - - /** - * Get the contents of an InputStream as a list of Strings, - * one entry per line, using the specified character encoding. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @param encoding the encoding to use, null means platform default - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static List readLines(InputStream input, Charset encoding) throws IOException { - InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(encoding)); - return readLines(reader); - } - - /** - * Get the contents of an InputStream as a list of Strings, - * one entry per line, using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input the InputStream to read from, not null - * @param encoding the encoding to use, null means platform default - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static List readLines(InputStream input, String encoding) throws IOException { - return readLines(input, Charsets.toCharset(encoding)); - } - - /** - * Get the contents of a Reader as a list of Strings, - * one entry per line. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - * - * @param input the Reader to read from, not null - * @return the list of Strings, never null - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static List readLines(Reader input) throws IOException { - BufferedReader reader = toBufferedReader(input); - List list = new ArrayList(); - String line = reader.readLine(); - while (line != null) { - list.add(line); - line = reader.readLine(); - } - return list; - } - - //----------------------------------------------------------------------- - - /** - * Convert the specified CharSequence to an input stream, encoded as bytes - * using the default character encoding of the platform. - * - * @param input the CharSequence to convert - * @return an input stream - * @since 2.0 - */ - public static InputStream toInputStream(CharSequence input) { - return toInputStream(input, Charset.defaultCharset()); - } - - /** - * Convert the specified CharSequence to an input stream, encoded as bytes - * using the specified character encoding. - * - * @param input the CharSequence to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @since 2.3 - */ - public static InputStream toInputStream(CharSequence input, Charset encoding) { - return toInputStream(input.toString(), encoding); - } - - /** - * Convert the specified CharSequence to an input stream, encoded as bytes - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - * - * @param input the CharSequence to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @throws java.io.IOException if the encoding is invalid - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.0 - */ - public static InputStream toInputStream(CharSequence input, String encoding) throws IOException { - return toInputStream(input, Charsets.toCharset(encoding)); - } - - //----------------------------------------------------------------------- - - /** - * Convert the specified string to an input stream, encoded as bytes - * using the default character encoding of the platform. - * - * @param input the string to convert - * @return an input stream - * @since 1.1 - */ - public static InputStream toInputStream(String input) { - return toInputStream(input, Charset.defaultCharset()); - } - - /** - * Convert the specified string to an input stream, encoded as bytes - * using the specified character encoding. - * - * @param input the string to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @since 2.3 - */ - public static InputStream toInputStream(String input, Charset encoding) { - return new ByteArrayInputStream(StringCodingUtils.getBytes(input, Charsets.toCharset(encoding))); - } - - /** - * Convert the specified string to an input stream, encoded as bytes - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - * - * @param input the string to convert - * @param encoding the encoding to use, null means platform default - * @return an input stream - * @throws java.io.IOException if the encoding is invalid - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static InputStream toInputStream(String input, String encoding) throws IOException { - byte[] bytes = StringCodingUtils.getBytes(input,Charsets.toCharset(encoding)); - return new ByteArrayInputStream(bytes); - } - - // write byte[] - //----------------------------------------------------------------------- - - /** - * Writes bytes from a byte[] to an OutputStream. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(byte[] data, OutputStream output) - throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the default character encoding of the platform. - *

    - * This method uses {@link String#String(byte[])}. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(byte[] data, Writer output) throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the specified character encoding. - *

    - * This method uses {@link String#String(byte[], String)}. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(byte[] data, Writer output, Charset encoding) throws IOException { - if (data != null) { - if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD){ - output.write(new String(data, Charsets.toCharset(encoding).name())); - }else{ - output.write(new String(data, Charsets.toCharset(encoding))); - } - } - } - - /** - * Writes bytes from a byte[] to chars on a Writer - * using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method uses {@link String#String(byte[], String)}. - * - * @param data the byte array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void write(byte[] data, Writer output, String encoding) throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write char[] - //----------------------------------------------------------------------- - - /** - * Writes chars from a char[] to a Writer - * using the default character encoding of the platform. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(char[] data, Writer output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream. - *

    - * This method uses {@link String#String(char[])} and - * {@link String#getBytes()}. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(char[] data, OutputStream output) - throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream using the specified character encoding. - *

    - * This method uses {@link String#String(char[])} and - * {@link String#getBytes(String)}. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(char[] data, OutputStream output, Charset encoding) throws IOException { - if (data != null) { - output.write(StringCodingUtils.getBytes(new String(data), Charsets.toCharset(encoding))); - } - } - - /** - * Writes chars from a char[] to bytes on an - * OutputStream using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method uses {@link String#String(char[])} and - * {@link String#getBytes(String)}. - * - * @param data the char array to write, do not modify during output, - * null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void write(char[] data, OutputStream output, String encoding) - throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write CharSequence - //----------------------------------------------------------------------- - - /** - * Writes chars from a CharSequence to a Writer. - * - * @param data the CharSequence to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static void write(CharSequence data, Writer output) throws IOException { - if (data != null) { - write(data.toString(), output); - } - } - - /** - * Writes chars from a CharSequence to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

    - * This method uses {@link String#getBytes()}. - * - * @param data the CharSequence to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static void write(CharSequence data, OutputStream output) - throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes chars from a CharSequence to bytes on an - * OutputStream using the specified character encoding. - *

    - * This method uses {@link String#getBytes(String)}. - * - * @param data the CharSequence to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(CharSequence data, OutputStream output, Charset encoding) throws IOException { - if (data != null) { - write(data.toString(), output, encoding); - } - } - - /** - * Writes chars from a CharSequence to bytes on an - * OutputStream using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method uses {@link String#getBytes(String)}. - * - * @param data the CharSequence to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 2.0 - */ - public static void write(CharSequence data, OutputStream output, String encoding) throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write String - //----------------------------------------------------------------------- - - /** - * Writes chars from a String to a Writer. - * - * @param data the String to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(String data, Writer output) throws IOException { - if (data != null) { - output.write(data); - } - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

    - * This method uses {@link String#getBytes()}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void write(String data, OutputStream output) - throws IOException { - write(data, output, Charset.defaultCharset()); - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the specified character encoding. - *

    - * This method uses {@link String#getBytes(String)}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void write(String data, OutputStream output, Charset encoding) throws IOException { - if (data != null) { - output.write(StringCodingUtils.getBytes(data, Charsets.toCharset(encoding))); - } - } - - /** - * Writes chars from a String to bytes on an - * OutputStream using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method uses {@link String#getBytes(String)}. - * - * @param data the String to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void write(String data, OutputStream output, String encoding) - throws IOException { - write(data, output, Charsets.toCharset(encoding)); - } - - // write StringBuffer - //----------------------------------------------------------------------- - - /** - * Writes chars from a StringBuffer to a Writer. - * - * @param data the StringBuffer to write, null ignored - * @param output the Writer to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - * @deprecated replaced by write(CharSequence, Writer) - */ - @Deprecated - public static void write(StringBuffer data, Writer output) - throws IOException { - if (data != null) { - output.write(data.toString()); - } - } - - /** - * Writes chars from a StringBuffer to bytes on an - * OutputStream using the default character encoding of the - * platform. - *

    - * This method uses {@link String#getBytes()}. - * - * @param data the StringBuffer to write, null ignored - * @param output the OutputStream to write to - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - * @deprecated replaced by write(CharSequence, OutputStream) - */ - @Deprecated - public static void write(StringBuffer data, OutputStream output) - throws IOException { - write(data, output, (String) null); - } - - /** - * Writes chars from a StringBuffer to bytes on an - * OutputStream using the specified character encoding. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method uses {@link String#getBytes(String)}. - * - * @param data the StringBuffer to write, null ignored - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - * @deprecated replaced by write(CharSequence, OutputStream, String) - */ - @Deprecated - public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException { - if (data != null) { - output.write(StringCodingUtils.getBytes(data.toString(), Charsets.toCharset(encoding))); - } - } - - // writeLines - //----------------------------------------------------------------------- - - /** - * Writes the toString() value of each item in a collection to - * an OutputStream line by line, using the default character - * encoding of the platform and the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @throws NullPointerException if the output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, - OutputStream output) throws IOException { - writeLines(lines, lineEnding, output, Charset.defaultCharset()); - } - - /** - * Writes the toString() value of each item in a collection to - * an OutputStream line by line, using the specified character - * encoding and the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void writeLines(Collection lines, String lineEnding, OutputStream output, Charset encoding) - throws IOException { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - Charset cs = Charsets.toCharset(encoding); - for (Object line : lines) { - if (line != null) { - output.write(StringCodingUtils.getBytes(line.toString(), cs)); - } - output.write(StringCodingUtils.getBytes(lineEnding,cs)); - } - } - - /** - * Writes the toString() value of each item in a collection to - * an OutputStream line by line, using the specified character - * encoding and the specified line ending. - *

    - * Character encoding names can be found at - * IANA. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param output the OutputStream to write to, not null, not closed - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, - OutputStream output, String encoding) throws IOException { - writeLines(lines, lineEnding, output, Charsets.toCharset(encoding)); - } - - /** - * Writes the toString() value of each item in a collection to - * a Writer line by line, using the specified line ending. - * - * @param lines the lines to write, null entries produce blank lines - * @param lineEnding the line separator to use, null is system default - * @param writer the Writer to write to, not null, not closed - * @throws NullPointerException if the input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void writeLines(Collection lines, String lineEnding, - Writer writer) throws IOException { - if (lines == null) { - return; - } - if (lineEnding == null) { - lineEnding = LINE_SEPARATOR; - } - for (Object line : lines) { - if (line != null) { - writer.write(line.toString()); - } - writer.write(lineEnding); - } - } - - // copy from InputStream - //----------------------------------------------------------------------- - - /** - * Copy bytes from an InputStream to an - * OutputStream. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * Large streams (over 2GB) will return a bytes copied value of - * -1 after the copy has completed since the correct - * number of bytes cannot be returned as an int. For large streams - * use the copyLarge(InputStream, OutputStream) method. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static int copy(InputStream input, OutputStream output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copy bytes from a large (over 2GB) InputStream to an - * OutputStream. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.3 - */ - public static long copyLarge(InputStream input, OutputStream output) - throws IOException { - return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy bytes from a large (over 2GB) InputStream to an - * OutputStream. - *

    - * This method uses the provided buffer, so there is no need to use a - * BufferedInputStream. - *

    - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @param buffer the buffer to use for the copy - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) - throws IOException { - long count = 0; - int n = 0; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Copy some or all bytes from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input bytes. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @param inputOffset : number of bytes to skip from input before copying - * -ve values are ignored - * @param length : number of bytes to copy. -ve means all - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(InputStream input, OutputStream output, long inputOffset, long length) - throws IOException { - return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy some or all bytes from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input bytes. - *

    - * This method uses the provided buffer, so there is no need to use a - * BufferedInputStream. - *

    - * - * @param input the InputStream to read from - * @param output the OutputStream to write to - * @param inputOffset : number of bytes to skip from input before copying - * -ve values are ignored - * @param length : number of bytes to copy. -ve means all - * @param buffer the buffer to use for the copy - * @return the number of bytes copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(InputStream input, OutputStream output, - final long inputOffset, final long length, byte[] buffer) throws IOException { - if (inputOffset > 0) { - skipFully(input, inputOffset); - } - if (length == 0) { - return 0; - } - final int bufferLength = buffer.length; - int bytesToRead = bufferLength; - if (length > 0 && length < bufferLength) { - bytesToRead = (int) length; - } - int read; - long totalRead = 0; - while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { - output.write(buffer, 0, read); - totalRead += read; - if (length > 0) { // only adjust length if not reading to the end - // Note the cast must work because buffer.length is an integer - bytesToRead = (int) Math.min(length - totalRead, bufferLength); - } - } - return totalRead; - } - - /** - * Copy bytes from an InputStream to chars on a - * Writer using the default character encoding of the platform. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * This method uses {@link java.io.InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void copy(InputStream input, Writer output) - throws IOException { - copy(input, output, Charset.defaultCharset()); - } - - /** - * Copy bytes from an InputStream to chars on a - * Writer using the specified character encoding. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * This method uses {@link java.io.InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void copy(InputStream input, Writer output, Charset encoding) throws IOException { - InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(encoding)); - copy(in, output); - } - - /** - * Copy bytes from an InputStream to chars on a - * Writer using the specified character encoding. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - *

    - * Character encoding names can be found at - * IANA. - *

    - * This method uses {@link java.io.InputStreamReader}. - * - * @param input the InputStream to read from - * @param output the Writer to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void copy(InputStream input, Writer output, String encoding) throws IOException { - copy(input, output, Charsets.toCharset(encoding)); - } - - // copy from Reader - //----------------------------------------------------------------------- - - /** - * Copy chars from a Reader to a Writer. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

    - * Large streams (over 2GB) will return a chars copied value of - * -1 after the copy has completed since the correct - * number of chars cannot be returned as an int. For large streams - * use the copyLarge(Reader, Writer) method. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied, or -1 if > Integer.MAX_VALUE - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static int copy(Reader input, Writer output) throws IOException { - long count = copyLarge(input, output); - if (count > Integer.MAX_VALUE) { - return -1; - } - return (int) count; - } - - /** - * Copy chars from a large (over 2GB) Reader to a Writer. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

    - * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.3 - */ - public static long copyLarge(Reader input, Writer output) throws IOException { - return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy chars from a large (over 2GB) Reader to a Writer. - *

    - * This method uses the provided buffer, so there is no need to use a - * BufferedReader. - *

    - * - * @param input the Reader to read from - * @param output the Writer to write to - * @param buffer the buffer to be used for the copy - * @return the number of characters copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException { - long count = 0; - int n = 0; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; - } - return count; - } - - /** - * Copy some or all chars from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input chars. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

    - * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. - * - * @param input the Reader to read from - * @param output the Writer to write to - * @param inputOffset : number of chars to skip from input before copying - * -ve values are ignored - * @param length : number of chars to copy. -ve means all - * @return the number of chars copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length) - throws IOException { - return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]); - } - - /** - * Copy some or all chars from a large (over 2GB) InputStream to an - * OutputStream, optionally skipping input chars. - *

    - * This method uses the provided buffer, so there is no need to use a - * BufferedReader. - *

    - * - * @param input the Reader to read from - * @param output the Writer to write to - * @param inputOffset : number of chars to skip from input before copying - * -ve values are ignored - * @param length : number of chars to copy. -ve means all - * @param buffer the buffer to be used for the copy - * @return the number of chars copied - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length, char[] buffer) - throws IOException { - if (inputOffset > 0) { - skipFully(input, inputOffset); - } - if (length == 0) { - return 0; - } - int bytesToRead = buffer.length; - if (length > 0 && length < buffer.length) { - bytesToRead = (int) length; - } - int read; - long totalRead = 0; - while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { - output.write(buffer, 0, read); - totalRead += read; - if (length > 0) { // only adjust length if not reading to the end - // Note the cast must work because buffer.length is an integer - bytesToRead = (int) Math.min(length - totalRead, buffer.length); - } - } - return totalRead; - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the default character encoding of the - * platform, and calling flush. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

    - * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

    - * This method uses {@link java.io.OutputStreamWriter}. - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static void copy(Reader input, OutputStream output) - throws IOException { - copy(input, output, Charset.defaultCharset()); - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the specified character encoding, and - * calling flush. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

    - *

    - * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

    - *

    - * This method uses {@link java.io.OutputStreamWriter}. - *

    - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.3 - */ - public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException { - OutputStreamWriter out = new OutputStreamWriter(output, Charsets.toCharset(encoding)); - copy(input, out); - // XXX Unless anyone is planning on rewriting OutputStreamWriter, - // we have to flush here. - out.flush(); - } - - /** - * Copy chars from a Reader to bytes on an - * OutputStream using the specified character encoding, and - * calling flush. - *

    - * This method buffers the input internally, so there is no need to use a - * BufferedReader. - *

    - * Character encoding names can be found at - * IANA. - *

    - * Due to the implementation of OutputStreamWriter, this method performs a - * flush. - *

    - * This method uses {@link java.io.OutputStreamWriter}. - * - * @param input the Reader to read from - * @param output the OutputStream to write to - * @param encoding the encoding to use, null means platform default - * @throws NullPointerException if the input or output is null - * @throws java.io.IOException if an I/O error occurs - * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not - * supported. - * @since 1.1 - */ - public static void copy(Reader input, OutputStream output, String encoding) throws IOException { - copy(input, output, Charsets.toCharset(encoding)); - } - - // content equals - //----------------------------------------------------------------------- - - /** - * Compare the contents of two Streams to determine if they are equal or - * not. - *

    - * This method buffers the input internally using - * BufferedInputStream if they are not already buffered. - * - * @param input1 the first stream - * @param input2 the second stream - * @return true if the content of the streams are equal or they both don't - * exist, false otherwise - * @throws NullPointerException if either input is null - * @throws java.io.IOException if an I/O error occurs - */ - public static boolean contentEquals(InputStream input1, InputStream input2) - throws IOException { - if (!(input1 instanceof BufferedInputStream)) { - input1 = new BufferedInputStream(input1); - } - if (!(input2 instanceof BufferedInputStream)) { - input2 = new BufferedInputStream(input2); - } - - int ch = input1.read(); - while (EOF != ch) { - int ch2 = input2.read(); - if (ch != ch2) { - return false; - } - ch = input1.read(); - } - - int ch2 = input2.read(); - return ch2 == EOF; - } - - /** - * Compare the contents of two Readers to determine if they are equal or - * not. - *

    - * This method buffers the input internally using - * BufferedReader if they are not already buffered. - * - * @param input1 the first reader - * @param input2 the second reader - * @return true if the content of the readers are equal or they both don't - * exist, false otherwise - * @throws NullPointerException if either input is null - * @throws java.io.IOException if an I/O error occurs - * @since 1.1 - */ - public static boolean contentEquals(Reader input1, Reader input2) - throws IOException { - - input1 = toBufferedReader(input1); - input2 = toBufferedReader(input2); - - int ch = input1.read(); - while (EOF != ch) { - int ch2 = input2.read(); - if (ch != ch2) { - return false; - } - ch = input1.read(); - } - - int ch2 = input2.read(); - return ch2 == EOF; - } - - /** - * Compare the contents of two Readers to determine if they are equal or - * not, ignoring EOL characters. - *

    - * This method buffers the input internally using - * BufferedReader if they are not already buffered. - * - * @param input1 the first reader - * @param input2 the second reader - * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise - * @throws NullPointerException if either input is null - * @throws java.io.IOException if an I/O error occurs - * @since 2.2 - */ - public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) - throws IOException { - BufferedReader br1 = toBufferedReader(input1); - BufferedReader br2 = toBufferedReader(input2); - - String line1 = br1.readLine(); - String line2 = br2.readLine(); - while (line1 != null && line2 != null && line1.equals(line2)) { - line1 = br1.readLine(); - line2 = br2.readLine(); - } - return line1 == null ? line2 == null ? true : false : line1.equals(line2); - } - - /** - * Skip bytes from an input byte stream. - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input byte stream to skip - * @param toSkip number of bytes to skip. - * @return number of bytes actually skipped. - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @see java.io.InputStream#skip(long) - * @since 2.0 - */ - public static long skip(InputStream input, long toSkip) throws IOException { - if (toSkip < 0) { - throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); - } - /* - * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data - * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer - * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) - */ - if (SKIP_BYTE_BUFFER == null) { - SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE]; - } - long remain = toSkip; - while (remain > 0) { - long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); - if (n < 0) { // EOF - break; - } - remain -= n; - } - return toSkip - remain; - } - - /** - * Skip characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input character stream to skip - * @param toSkip number of characters to skip. - * @return number of characters actually skipped. - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @see java.io.Reader#skip(long) - * @since 2.0 - */ - public static long skip(Reader input, long toSkip) throws IOException { - if (toSkip < 0) { - throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); - } - /* - * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data - * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer - * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) - */ - if (SKIP_CHAR_BUFFER == null) { - SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE]; - } - long remain = toSkip; - while (remain > 0) { - long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); - if (n < 0) { // EOF - break; - } - remain -= n; - } - return toSkip - remain; - } - - /** - * Skip the requested number of bytes or fail if there are not enough left. - *

    - * This allows for the possibility that {@link java.io.InputStream#skip(long)} may - * not skip as many bytes as requested (most likely because of reaching EOF). - * - * @param input stream to skip - * @param toSkip the number of bytes to skip - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @throws java.io.EOFException if the number of bytes skipped was incorrect - * @see java.io.InputStream#skip(long) - * @since 2.0 - */ - public static void skipFully(InputStream input, long toSkip) throws IOException { - if (toSkip < 0) { - throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); - } - long skipped = skip(input, toSkip); - if (skipped != toSkip) { - throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); - } - } - - /** - * Skip the requested number of characters or fail if there are not enough left. - *

    - * This allows for the possibility that {@link java.io.Reader#skip(long)} may - * not skip as many characters as requested (most likely because of reaching EOF). - * - * @param input stream to skip - * @param toSkip the number of characters to skip - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if toSkip is negative - * @throws java.io.EOFException if the number of characters skipped was incorrect - * @see java.io.Reader#skip(long) - * @since 2.0 - */ - public static void skipFully(Reader input, long toSkip) throws IOException { - long skipped = skip(input, toSkip); - if (skipped != toSkip) { - throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped); - } - } - - - /** - * Read characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(Reader input, char[] buffer, int offset, int length) throws IOException { - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative: " + length); - } - int remaining = length; - while (remaining > 0) { - int location = length - remaining; - int count = input.read(buffer, offset + location, remaining); - if (EOF == count) { // EOF - break; - } - remaining -= count; - } - return length - remaining; - } - - /** - * Read characters from an input character stream. - * This implementation guarantees that it will read as many characters - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.Reader}. - * - * @param input where to read input from - * @param buffer destination - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(Reader input, char[] buffer) throws IOException { - return read(input, buffer, 0, buffer.length); - } - - /** - * Read bytes from an input stream. - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.InputStream}. - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException { - if (length < 0) { - throw new IllegalArgumentException("Length must not be negative: " + length); - } - int remaining = length; - while (remaining > 0) { - int location = length - remaining; - int count = input.read(buffer, offset + location, remaining); - if (EOF == count) { // EOF - break; - } - remaining -= count; - } - return length - remaining; - } - - /** - * Read bytes from an input stream. - * This implementation guarantees that it will read as many bytes - * as possible before giving up; this may not always be the case for - * subclasses of {@link java.io.InputStream}. - * - * @param input where to read input from - * @param buffer destination - * @return actual length read; may be less than requested if EOF was reached - * @throws java.io.IOException if a read error occurs - * @since 2.2 - */ - public static int read(InputStream input, byte[] buffer) throws IOException { - return read(input, buffer, 0, buffer.length); - } - - /** - * Read the requested number of characters or fail if there are not enough left. - *

    - * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may - * not read as many characters as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of characters read was incorrect - * @since 2.2 - */ - public static void readFully(Reader input, char[] buffer, int offset, int length) throws IOException { - int actual = read(input, buffer, offset, length); - if (actual != length) { - throw new EOFException("Length to read: " + length + " actual: " + actual); - } - } - - /** - * Read the requested number of characters or fail if there are not enough left. - *

    - * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may - * not read as many characters as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of characters read was incorrect - * @since 2.2 - */ - public static void readFully(Reader input, char[] buffer) throws IOException { - readFully(input, buffer, 0, buffer.length); - } - - /** - * Read the requested number of bytes or fail if there are not enough left. - *

    - * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @param offset inital offset into buffer - * @param length length to read, must be >= 0 - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of bytes read was incorrect - * @since 2.2 - */ - public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException { - int actual = read(input, buffer, offset, length); - if (actual != length) { - throw new EOFException("Length to read: " + length + " actual: " + actual); - } - } - - /** - * Read the requested number of bytes or fail if there are not enough left. - *

    - * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may - * not read as many bytes as requested (most likely because of reaching EOF). - * - * @param input where to read input from - * @param buffer destination - * @throws java.io.IOException if there is a problem reading the file - * @throws IllegalArgumentException if length is negative - * @throws java.io.EOFException if the number of bytes read was incorrect - * @since 2.2 - */ - public static void readFully(InputStream input, byte[] buffer) throws IOException { - readFully(input, buffer, 0, buffer.length); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io; + +import android.os.Build; +import com.litesuits.common.io.stream.*; + +import java.io.*; +import java.net.*; +import java.nio.channels.Selector; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * General IO stream manipulation utilities. + *

    + * This class provides static utility methods for input/output operations. + *

      + *
    • closeQuietly - these methods close a stream ignoring nulls and exceptions + *
    • toXxx/read - these methods read data from a stream + *
    • write - these methods write data to a stream + *
    • copy - these methods copy all the data from one stream to another + *
    • contentEquals - these methods compare the content of two streams + *
    + *

    + * The byte-to-char methods and char-to-byte methods involve a conversion step. + * Two methods are provided in each case, one that uses the platform default + * encoding and the other which allows you to specify an encoding. You are + * encouraged to always specify an encoding because relying on the platform + * default can lead to unexpected results, for example when moving from + * development to production. + *

    + * All the methods in this class that read a stream are buffered internally. + * This means that there is no cause to use a BufferedInputStream + * or BufferedReader. The default buffer size of 4K has been shown + * to be efficient in tests. + *

    + * Wherever possible, the methods in this class do not flush or close + * the stream. This is to avoid making non-portable assumptions about the + * streams' origin and further use. Thus the caller is still responsible for + * closing streams after use. + *

    + * Origin of code: Excalibur. + * + * @version $Id: IOUtils.java 1326636 2012-04-16 14:54:53Z ggregory $ + */ +public class IOUtils { + // NOTE: This class is focussed on InputStream, OutputStream, Reader and + // Writer. Each method should take at least one of these as a parameter, + // or return one of them. + + private static final int EOF = -1; + /** + * The Unix directory separator character. + */ + public static final char DIR_SEPARATOR_UNIX = '/'; + /** + * The Windows directory separator character. + */ + public static final char DIR_SEPARATOR_WINDOWS = '\\'; + /** + * The system directory separator character. + */ + public static final char DIR_SEPARATOR = File.separatorChar; + /** + * The Unix line separator string. + */ + public static final String LINE_SEPARATOR_UNIX = "\n"; + /** + * The Windows line separator string. + */ + public static final String LINE_SEPARATOR_WINDOWS = "\r\n"; + /** + * The system line separator string. + */ + public static final String LINE_SEPARATOR; + + static { + // avoid security issues + StringBuilderWriter buf = new StringBuilderWriter(4); + PrintWriter out = new PrintWriter(buf); + out.println(); + LINE_SEPARATOR = buf.toString(); + out.close(); + } + + /** + * The default buffer size ({@value}) to use for + * {@link #copyLarge(java.io.InputStream, java.io.OutputStream)} + * and + * {@link #copyLarge(java.io.Reader, java.io.Writer)} + */ + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + + /** + * The default buffer size to use for the skip() methods. + */ + private static final int SKIP_BUFFER_SIZE = 2048; + + // Allocated in the relevant skip method if necessary. + /* + * N.B. no need to synchronize these because: + * - we don't care if the buffer is created multiple times (the data is ignored) + * - we always use the same size buffer, so if it it is recreated it will still be OK + * (if the buffer size were variable, we would need to synch. to ensure some other thread + * did not create a smaller one) + */ + private static char[] SKIP_CHAR_BUFFER; + private static byte[] SKIP_BYTE_BUFFER; + + /** + * Instances should NOT be constructed in standard programming. + */ + public IOUtils() { + super(); + } + + //----------------------------------------------------------------------- + + /** + * Closes a URLConnection. + * + * @param conn the connection to close. + * @since 2.4 + */ + public static void close(URLConnection conn) { + if (conn instanceof HttpURLConnection) { + ((HttpURLConnection) conn).disconnect(); + } + } + + /** + * Unconditionally close an Reader. + *

    + * Equivalent to {@link java.io.Reader#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   char[] data = new char[1024];
    +     *   Reader in = null;
    +     *   try {
    +     *       in = new FileReader("foo.txt");
    +     *       in.read(data);
    +     *       in.close(); //close errors are handled
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(in);
    +     *   }
    +     * 
    + * + * @param input the Reader to close, may be null or already closed + */ + public static void closeQuietly(Reader input) { + closeQuietly((Closeable) input); + } + + /** + * Unconditionally close a Writer. + *

    + * Equivalent to {@link java.io.Writer#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   Writer out = null;
    +     *   try {
    +     *       out = new StringWriter();
    +     *       out.write("Hello World");
    +     *       out.close(); //close errors are handled
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(out);
    +     *   }
    +     * 
    + * + * @param output the Writer to close, may be null or already closed + */ + public static void closeQuietly(Writer output) { + closeQuietly((Closeable) output); + } + + /** + * Unconditionally close an InputStream. + *

    + * Equivalent to {@link java.io.InputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   byte[] data = new byte[1024];
    +     *   InputStream in = null;
    +     *   try {
    +     *       in = new FileInputStream("foo.txt");
    +     *       in.read(data);
    +     *       in.close(); //close errors are handled
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(in);
    +     *   }
    +     * 
    + * + * @param input the InputStream to close, may be null or already closed + */ + public static void closeQuietly(InputStream input) { + closeQuietly((Closeable) input); + } + + /** + * Unconditionally close an OutputStream. + *

    + * Equivalent to {@link java.io.OutputStream#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     * byte[] data = "Hello, World".getBytes();
    +     *
    +     * OutputStream out = null;
    +     * try {
    +     *     out = new FileOutputStream("foo.txt");
    +     *     out.write(data);
    +     *     out.close(); //close errors are handled
    +     * } catch (IOException e) {
    +     *     // error handling
    +     * } finally {
    +     *     IOUtils.closeQuietly(out);
    +     * }
    +     * 
    + * + * @param output the OutputStream to close, may be null or already closed + */ + public static void closeQuietly(OutputStream output) { + closeQuietly((Closeable) output); + } + + /** + * Unconditionally close a Closeable. + *

    + * Equivalent to {@link java.io.Closeable#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   Closeable closeable = null;
    +     *   try {
    +     *       closeable = new FileReader("foo.txt");
    +     *       // process closeable
    +     *       closeable.close();
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(closeable);
    +     *   }
    +     * 
    + * + * @param closeable the object to close, may be null or already closed + * @since 2.0 + */ + public static void closeQuietly(Closeable closeable) { + try { + if (closeable != null) { + closeable.close(); + } + } catch (IOException ioe) { + // ignore + } + } + + /** + * Unconditionally close a Socket. + *

    + * Equivalent to {@link java.net.Socket#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   Socket socket = null;
    +     *   try {
    +     *       socket = new Socket("http://www.foo.com/", 80);
    +     *       // process socket
    +     *       socket.close();
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(socket);
    +     *   }
    +     * 
    + * + * @param sock the Socket to close, may be null or already closed + * @since 2.0 + */ + public static void closeQuietly(Socket sock) { + if (sock != null) { + try { + sock.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Unconditionally close a Selector. + *

    + * Equivalent to {@link java.nio.channels.Selector#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   Selector selector = null;
    +     *   try {
    +     *       selector = Selector.open();
    +     *       // process socket
    +     *
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(selector);
    +     *   }
    +     * 
    + * + * @param selector the Selector to close, may be null or already closed + * @since 2.2 + */ + public static void closeQuietly(Selector selector) { + if (selector != null) { + try { + selector.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Unconditionally close a ServerSocket. + *

    + * Equivalent to {@link java.net.ServerSocket#close()}, except any exceptions will be ignored. + * This is typically used in finally blocks. + *

    + * Example code: + *

    +     *   ServerSocket socket = null;
    +     *   try {
    +     *       socket = new ServerSocket();
    +     *       // process socket
    +     *       socket.close();
    +     *   } catch (Exception e) {
    +     *       // error handling
    +     *   } finally {
    +     *       IOUtils.closeQuietly(socket);
    +     *   }
    +     * 
    + * + * @param sock the ServerSocket to close, may be null or already closed + * @since 2.2 + */ + public static void closeQuietly(ServerSocket sock) { + if (sock != null) { + try { + sock.close(); + } catch (IOException ioe) { + // ignored + } + } + } + + /** + * Fetches entire contents of an InputStream and represent + * same data as result InputStream. + *

    + * This method is useful where, + *

      + *
    • Source InputStream is slow.
    • + *
    • It has network resources associated, so we cannot keep it open for + * long time.
    • + *
    • It has network timeout associated.
    • + *
    + * It can be used in favor of {@link #toByteArray(java.io.InputStream)}, since it + * avoids unnecessary allocation and copy of byte[].
    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input Stream to be fully buffered. + * @return A fully buffered stream. + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static InputStream toBufferedInputStream(InputStream input) throws IOException { + return com.litesuits.common.io.stream.ByteArrayOutputStream.toBufferedInputStream(input); + } + + /** + * Returns the given reader if it is a {@link java.io.BufferedReader}, otherwise creates a toBufferedReader for the given + * reader. + * + * @param reader the reader to wrap or return + * @return the given reader or a new {@link java.io.BufferedReader} for the given reader + * @since 2.2 + */ + public static BufferedReader toBufferedReader(Reader reader) { + return reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader); + } + + // read toByteArray + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a byte[]. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static byte[] toByteArray(InputStream input) throws IOException { + com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); + copy(input, output); + return output.toByteArray(); + } + + /** + * Get contents of an InputStream as a byte[]. + * Use this method instead of toByteArray(InputStream) + * when InputStream size is known. + * NOTE: the method checks that the length can safely be cast to an int without truncation + * before using {@link com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int)} to read into the byte array. + * (Arrays can have no more than Integer.MAX_VALUE entries anyway) + * + * @param input the InputStream to read from + * @param size the size of InputStream + * @return the requested byte array + * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size + * @throws IllegalArgumentException if size is less than zero or size is greater than Integer.MAX_VALUE + * @see com.litesuits.common.io.IOUtils#toByteArray(java.io.InputStream, int) + * @since 2.1 + */ + public static byte[] toByteArray(InputStream input, long size) throws IOException { + + if (size > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Size cannot be greater than Integer max value: " + size); + } + + return toByteArray(input, (int) size); + } + + /** + * Get the contents of an InputStream as a byte[]. + * Use this method instead of toByteArray(InputStream) + * when InputStream size is known + * + * @param input the InputStream to read from + * @param size the size of InputStream + * @return the requested byte array + * @throws java.io.IOException if an I/O error occurs or InputStream size differ from parameter size + * @throws IllegalArgumentException if size is less than zero + * @since 2.1 + */ + public static byte[] toByteArray(InputStream input, int size) throws IOException { + + if (size < 0) { + throw new IllegalArgumentException("Size must be equal or greater than zero: " + size); + } + + if (size == 0) { + return new byte[0]; + } + + byte[] data = new byte[size]; + int offset = 0; + int readed; + + while (offset < size && (readed = input.read(data, offset, size - offset)) != EOF) { + offset += readed; + } + + if (offset != size) { + throw new IOException("Unexpected readed size. current: " + offset + ", excepted: " + size); + } + + return data; + } + + /** + * Get the contents of a Reader as a byte[] + * using the default character encoding of the platform. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static byte[] toByteArray(Reader input) throws IOException { + return toByteArray(input, Charset.defaultCharset()); + } + + /** + * Get the contents of a Reader as a byte[] + * using the specified character encoding. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static byte[] toByteArray(Reader input, Charset encoding) throws IOException { + com.litesuits.common.io.stream.ByteArrayOutputStream output = new com.litesuits.common.io.stream.ByteArrayOutputStream(); + copy(input, output, encoding); + return output.toByteArray(); + } + + /** + * Get the contents of a Reader as a byte[] + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @param encoding the encoding to use, null means platform default + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static byte[] toByteArray(Reader input, String encoding) throws IOException { + return toByteArray(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a String as a byte[] + * using the default character encoding of the platform. + *

    + * This is the same as {@link String#getBytes()}. + * + * @param input the String to convert + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#getBytes()} + */ + @Deprecated + public static byte[] toByteArray(String input) throws IOException { + return input.getBytes(); + } + + /** + * Get the contents of a URI as a byte[]. + * + * @param uri the URI to read + * @return the requested byte array + * @throws NullPointerException if the uri is null + * @throws java.io.IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URI uri) throws IOException { + return IOUtils.toByteArray(uri.toURL()); + } + + /** + * Get the contents of a URL as a byte[]. + * + * @param url the URL to read + * @return the requested byte array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URL url) throws IOException { + URLConnection conn = url.openConnection(); + try { + return IOUtils.toByteArray(conn); + } finally { + close(conn); + } + } + + /** + * Get the contents of a URLConnection as a byte[]. + * + * @param urlConn the URLConnection to read + * @return the requested byte array + * @throws NullPointerException if the urlConn is null + * @throws java.io.IOException if an I/O exception occurs + * @since 2.4 + */ + public static byte[] toByteArray(URLConnection urlConn) throws IOException { + InputStream inputStream = urlConn.getInputStream(); + try { + return IOUtils.toByteArray(inputStream); + } finally { + inputStream.close(); + } + } + + // read char[] + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a character array + * using the default character encoding of the platform. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static char[] toCharArray(InputStream is) throws IOException { + return toCharArray(is, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a character array + * using the specified character encoding. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static char[] toCharArray(InputStream is, Charset encoding) + throws IOException { + CharArrayWriter output = new CharArrayWriter(); + copy(is, output, encoding); + return output.toCharArray(); + } + + /** + * Get the contents of an InputStream as a character array + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param is the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static char[] toCharArray(InputStream is, String encoding) throws IOException { + return toCharArray(is, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a character array. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested character array + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static char[] toCharArray(Reader input) throws IOException { + CharArrayWriter sw = new CharArrayWriter(); + copy(input, sw); + return sw.toCharArray(); + } + + // read toString + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a String + * using the default character encoding of the platform. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static String toString(InputStream input) throws IOException { + return toString(input, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a String + * using the specified character encoding. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static String toString(InputStream input, Charset encoding) throws IOException { + StringBuilderWriter sw = new StringBuilderWriter(); + copy(input, sw, encoding); + return sw.toString(); + } + + /** + * Get the contents of an InputStream as a String + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + */ + public static String toString(InputStream input, String encoding) + throws IOException { + return toString(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a String. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static String toString(Reader input) throws IOException { + StringBuilderWriter sw = new StringBuilderWriter(); + copy(input, sw); + return sw.toString(); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.1 + */ + public static String toString(URI uri) throws IOException { + return toString(uri, Charset.defaultCharset()); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.3. + */ + public static String toString(URI uri, Charset encoding) throws IOException { + return toString(uri.toURL(), Charsets.toCharset(encoding)); + } + + /** + * Gets the contents at the given URI. + * + * @param uri The URI source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.1 + */ + public static String toString(URI uri, String encoding) throws IOException { + return toString(uri, Charsets.toCharset(encoding)); + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.1 + */ + public static String toString(URL url) throws IOException { + return toString(url, Charset.defaultCharset()); + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @since 2.3 + */ + public static String toString(URL url, Charset encoding) throws IOException { + InputStream inputStream = url.openStream(); + try { + return toString(inputStream, encoding); + } finally { + inputStream.close(); + } + } + + /** + * Gets the contents at the given URL. + * + * @param url The URL source. + * @param encoding The encoding name for the URL contents. + * @return The contents of the URL as a String. + * @throws java.io.IOException if an I/O exception occurs. + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.1 + */ + public static String toString(URL url, String encoding) throws IOException { + return toString(url, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a byte[] as a String + * using the default character encoding of the platform. + * + * @param input the byte array to read from + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs (never occurs) + * @deprecated Use {@link String#String(byte[])} + */ + @Deprecated + public static String toString(byte[] input) throws IOException { + return new String(input); + } + + /** + * Get the contents of a byte[] as a String + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + * + * @param input the byte array to read from + * @param encoding the encoding to use, null means platform default + * @return the requested String + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs (never occurs) + */ + public static String toString(byte[] input, String encoding) throws IOException { + return new String(input, encoding); + } + + // readLines + //----------------------------------------------------------------------- + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the default character encoding of the platform. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static List readLines(InputStream input) throws IOException { + return readLines(input, Charset.defaultCharset()); + } + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static List readLines(InputStream input, Charset encoding) throws IOException { + InputStreamReader reader = new InputStreamReader(input, Charsets.toCharset(encoding)); + return readLines(reader); + } + + /** + * Get the contents of an InputStream as a list of Strings, + * one entry per line, using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input the InputStream to read from, not null + * @param encoding the encoding to use, null means platform default + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static List readLines(InputStream input, String encoding) throws IOException { + return readLines(input, Charsets.toCharset(encoding)); + } + + /** + * Get the contents of a Reader as a list of Strings, + * one entry per line. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + * + * @param input the Reader to read from, not null + * @return the list of Strings, never null + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static List readLines(Reader input) throws IOException { + BufferedReader reader = toBufferedReader(input); + List list = new ArrayList(); + String line = reader.readLine(); + while (line != null) { + list.add(line); + line = reader.readLine(); + } + return list; + } + + //----------------------------------------------------------------------- + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the CharSequence to convert + * @return an input stream + * @since 2.0 + */ + public static InputStream toInputStream(CharSequence input) { + return toInputStream(input, Charset.defaultCharset()); + } + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the specified character encoding. + * + * @param input the CharSequence to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @since 2.3 + */ + public static InputStream toInputStream(CharSequence input, Charset encoding) { + return toInputStream(input.toString(), encoding); + } + + /** + * Convert the specified CharSequence to an input stream, encoded as bytes + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + * + * @param input the CharSequence to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @throws java.io.IOException if the encoding is invalid + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.0 + */ + public static InputStream toInputStream(CharSequence input, String encoding) throws IOException { + return toInputStream(input, Charsets.toCharset(encoding)); + } + + //----------------------------------------------------------------------- + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the default character encoding of the platform. + * + * @param input the string to convert + * @return an input stream + * @since 1.1 + */ + public static InputStream toInputStream(String input) { + return toInputStream(input, Charset.defaultCharset()); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @since 2.3 + */ + public static InputStream toInputStream(String input, Charset encoding) { + return new ByteArrayInputStream(StringCodingUtils.getBytes(input, Charsets.toCharset(encoding))); + } + + /** + * Convert the specified string to an input stream, encoded as bytes + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + * + * @param input the string to convert + * @param encoding the encoding to use, null means platform default + * @return an input stream + * @throws java.io.IOException if the encoding is invalid + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static InputStream toInputStream(String input, String encoding) throws IOException { + byte[] bytes = StringCodingUtils.getBytes(input,Charsets.toCharset(encoding)); + return new ByteArrayInputStream(bytes); + } + + // write byte[] + //----------------------------------------------------------------------- + + /** + * Writes bytes from a byte[] to an OutputStream. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(byte[] data, OutputStream output) + throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the default character encoding of the platform. + *

    + * This method uses {@link String#String(byte[])}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(byte[] data, Writer output) throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the specified character encoding. + *

    + * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(byte[] data, Writer output, Charset encoding) throws IOException { + if (data != null) { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD){ + output.write(new String(data, Charsets.toCharset(encoding).name())); + }else{ + output.write(new String(data, Charsets.toCharset(encoding))); + } + } + } + + /** + * Writes bytes from a byte[] to chars on a Writer + * using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method uses {@link String#String(byte[], String)}. + * + * @param data the byte array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(byte[] data, Writer output, String encoding) throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write char[] + //----------------------------------------------------------------------- + + /** + * Writes chars from a char[] to a Writer + * using the default character encoding of the platform. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(char[] data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream. + *

    + * This method uses {@link String#String(char[])} and + * {@link String#getBytes()}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(char[] data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream using the specified character encoding. + *

    + * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(char[] data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(StringCodingUtils.getBytes(new String(data), Charsets.toCharset(encoding))); + } + } + + /** + * Writes chars from a char[] to bytes on an + * OutputStream using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method uses {@link String#String(char[])} and + * {@link String#getBytes(String)}. + * + * @param data the char array to write, do not modify during output, + * null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(char[] data, OutputStream output, String encoding) + throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write CharSequence + //----------------------------------------------------------------------- + + /** + * Writes chars from a CharSequence to a Writer. + * + * @param data the CharSequence to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static void write(CharSequence data, Writer output) throws IOException { + if (data != null) { + write(data.toString(), output); + } + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

    + * This method uses {@link String#getBytes()}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static void write(CharSequence data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the specified character encoding. + *

    + * This method uses {@link String#getBytes(String)}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(CharSequence data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + write(data.toString(), output, encoding); + } + } + + /** + * Writes chars from a CharSequence to bytes on an + * OutputStream using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method uses {@link String#getBytes(String)}. + * + * @param data the CharSequence to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 2.0 + */ + public static void write(CharSequence data, OutputStream output, String encoding) throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write String + //----------------------------------------------------------------------- + + /** + * Writes chars from a String to a Writer. + * + * @param data the String to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(String data, Writer output) throws IOException { + if (data != null) { + output.write(data); + } + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

    + * This method uses {@link String#getBytes()}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void write(String data, OutputStream output) + throws IOException { + write(data, output, Charset.defaultCharset()); + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the specified character encoding. + *

    + * This method uses {@link String#getBytes(String)}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void write(String data, OutputStream output, Charset encoding) throws IOException { + if (data != null) { + output.write(StringCodingUtils.getBytes(data, Charsets.toCharset(encoding))); + } + } + + /** + * Writes chars from a String to bytes on an + * OutputStream using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method uses {@link String#getBytes(String)}. + * + * @param data the String to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void write(String data, OutputStream output, String encoding) + throws IOException { + write(data, output, Charsets.toCharset(encoding)); + } + + // write StringBuffer + //----------------------------------------------------------------------- + + /** + * Writes chars from a StringBuffer to a Writer. + * + * @param data the StringBuffer to write, null ignored + * @param output the Writer to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + * @deprecated replaced by write(CharSequence, Writer) + */ + @Deprecated + public static void write(StringBuffer data, Writer output) + throws IOException { + if (data != null) { + output.write(data.toString()); + } + } + + /** + * Writes chars from a StringBuffer to bytes on an + * OutputStream using the default character encoding of the + * platform. + *

    + * This method uses {@link String#getBytes()}. + * + * @param data the StringBuffer to write, null ignored + * @param output the OutputStream to write to + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + * @deprecated replaced by write(CharSequence, OutputStream) + */ + @Deprecated + public static void write(StringBuffer data, OutputStream output) + throws IOException { + write(data, output, (String) null); + } + + /** + * Writes chars from a StringBuffer to bytes on an + * OutputStream using the specified character encoding. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method uses {@link String#getBytes(String)}. + * + * @param data the StringBuffer to write, null ignored + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + * @deprecated replaced by write(CharSequence, OutputStream, String) + */ + @Deprecated + public static void write(StringBuffer data, OutputStream output, String encoding) throws IOException { + if (data != null) { + output.write(StringCodingUtils.getBytes(data.toString(), Charsets.toCharset(encoding))); + } + } + + // writeLines + //----------------------------------------------------------------------- + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the default character + * encoding of the platform and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @throws NullPointerException if the output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + OutputStream output) throws IOException { + writeLines(lines, lineEnding, output, Charset.defaultCharset()); + } + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the specified character + * encoding and the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void writeLines(Collection lines, String lineEnding, OutputStream output, Charset encoding) + throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + Charset cs = Charsets.toCharset(encoding); + for (Object line : lines) { + if (line != null) { + output.write(StringCodingUtils.getBytes(line.toString(), cs)); + } + output.write(StringCodingUtils.getBytes(lineEnding,cs)); + } + } + + /** + * Writes the toString() value of each item in a collection to + * an OutputStream line by line, using the specified character + * encoding and the specified line ending. + *

    + * Character encoding names can be found at + * IANA. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param output the OutputStream to write to, not null, not closed + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + OutputStream output, String encoding) throws IOException { + writeLines(lines, lineEnding, output, Charsets.toCharset(encoding)); + } + + /** + * Writes the toString() value of each item in a collection to + * a Writer line by line, using the specified line ending. + * + * @param lines the lines to write, null entries produce blank lines + * @param lineEnding the line separator to use, null is system default + * @param writer the Writer to write to, not null, not closed + * @throws NullPointerException if the input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void writeLines(Collection lines, String lineEnding, + Writer writer) throws IOException { + if (lines == null) { + return; + } + if (lineEnding == null) { + lineEnding = LINE_SEPARATOR; + } + for (Object line : lines) { + if (line != null) { + writer.write(line.toString()); + } + writer.write(lineEnding); + } + } + + // copy from InputStream + //----------------------------------------------------------------------- + + /** + * Copy bytes from an InputStream to an + * OutputStream. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * Large streams (over 2GB) will return a bytes copied value of + * -1 after the copy has completed since the correct + * number of bytes cannot be returned as an int. For large streams + * use the copyLarge(InputStream, OutputStream) method. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(InputStream input, OutputStream output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(InputStream input, OutputStream output) + throws IOException { + return copyLarge(input, output, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy bytes from a large (over 2GB) InputStream to an + * OutputStream. + *

    + * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

    + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, byte[] buffer) + throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy some or all bytes from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input bytes. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param inputOffset : number of bytes to skip from input before copying + * -ve values are ignored + * @param length : number of bytes to copy. -ve means all + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, long inputOffset, long length) + throws IOException { + return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy some or all bytes from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input bytes. + *

    + * This method uses the provided buffer, so there is no need to use a + * BufferedInputStream. + *

    + * + * @param input the InputStream to read from + * @param output the OutputStream to write to + * @param inputOffset : number of bytes to skip from input before copying + * -ve values are ignored + * @param length : number of bytes to copy. -ve means all + * @param buffer the buffer to use for the copy + * @return the number of bytes copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(InputStream input, OutputStream output, + final long inputOffset, final long length, byte[] buffer) throws IOException { + if (inputOffset > 0) { + skipFully(input, inputOffset); + } + if (length == 0) { + return 0; + } + final int bufferLength = buffer.length; + int bytesToRead = bufferLength; + if (length > 0 && length < bufferLength) { + bytesToRead = (int) length; + } + int read; + long totalRead = 0; + while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { + output.write(buffer, 0, read); + totalRead += read; + if (length > 0) { // only adjust length if not reading to the end + // Note the cast must work because buffer.length is an integer + bytesToRead = (int) Math.min(length - totalRead, bufferLength); + } + } + return totalRead; + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the default character encoding of the platform. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * This method uses {@link java.io.InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void copy(InputStream input, Writer output) + throws IOException { + copy(input, output, Charset.defaultCharset()); + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * This method uses {@link java.io.InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void copy(InputStream input, Writer output, Charset encoding) throws IOException { + InputStreamReader in = new InputStreamReader(input, Charsets.toCharset(encoding)); + copy(in, output); + } + + /** + * Copy bytes from an InputStream to chars on a + * Writer using the specified character encoding. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + *

    + * Character encoding names can be found at + * IANA. + *

    + * This method uses {@link java.io.InputStreamReader}. + * + * @param input the InputStream to read from + * @param output the Writer to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void copy(InputStream input, Writer output, String encoding) throws IOException { + copy(input, output, Charsets.toCharset(encoding)); + } + + // copy from Reader + //----------------------------------------------------------------------- + + /** + * Copy chars from a Reader to a Writer. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

    + * Large streams (over 2GB) will return a chars copied value of + * -1 after the copy has completed since the correct + * number of chars cannot be returned as an int. For large streams + * use the copyLarge(Reader, Writer) method. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied, or -1 if > Integer.MAX_VALUE + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static int copy(Reader input, Writer output) throws IOException { + long count = copyLarge(input, output); + if (count > Integer.MAX_VALUE) { + return -1; + } + return (int) count; + } + + /** + * Copy chars from a large (over 2GB) Reader to a Writer. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

    + * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.3 + */ + public static long copyLarge(Reader input, Writer output) throws IOException { + return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy chars from a large (over 2GB) Reader to a Writer. + *

    + * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

    + * + * @param input the Reader to read from + * @param output the Writer to write to + * @param buffer the buffer to be used for the copy + * @return the number of characters copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, char[] buffer) throws IOException { + long count = 0; + int n = 0; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; + } + + /** + * Copy some or all chars from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input chars. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

    + * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}. + * + * @param input the Reader to read from + * @param output the Writer to write to + * @param inputOffset : number of chars to skip from input before copying + * -ve values are ignored + * @param length : number of chars to copy. -ve means all + * @return the number of chars copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length) + throws IOException { + return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]); + } + + /** + * Copy some or all chars from a large (over 2GB) InputStream to an + * OutputStream, optionally skipping input chars. + *

    + * This method uses the provided buffer, so there is no need to use a + * BufferedReader. + *

    + * + * @param input the Reader to read from + * @param output the Writer to write to + * @param inputOffset : number of chars to skip from input before copying + * -ve values are ignored + * @param length : number of chars to copy. -ve means all + * @param buffer the buffer to be used for the copy + * @return the number of chars copied + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static long copyLarge(Reader input, Writer output, final long inputOffset, final long length, char[] buffer) + throws IOException { + if (inputOffset > 0) { + skipFully(input, inputOffset); + } + if (length == 0) { + return 0; + } + int bytesToRead = buffer.length; + if (length > 0 && length < buffer.length) { + bytesToRead = (int) length; + } + int read; + long totalRead = 0; + while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) { + output.write(buffer, 0, read); + totalRead += read; + if (length > 0) { // only adjust length if not reading to the end + // Note the cast must work because buffer.length is an integer + bytesToRead = (int) Math.min(length - totalRead, buffer.length); + } + } + return totalRead; + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the default character encoding of the + * platform, and calling flush. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

    + * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

    + * This method uses {@link java.io.OutputStreamWriter}. + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static void copy(Reader input, OutputStream output) + throws IOException { + copy(input, output, Charset.defaultCharset()); + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the specified character encoding, and + * calling flush. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

    + *

    + * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

    + *

    + * This method uses {@link java.io.OutputStreamWriter}. + *

    + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.3 + */ + public static void copy(Reader input, OutputStream output, Charset encoding) throws IOException { + OutputStreamWriter out = new OutputStreamWriter(output, Charsets.toCharset(encoding)); + copy(input, out); + // XXX Unless anyone is planning on rewriting OutputStreamWriter, + // we have to flush here. + out.flush(); + } + + /** + * Copy chars from a Reader to bytes on an + * OutputStream using the specified character encoding, and + * calling flush. + *

    + * This method buffers the input internally, so there is no need to use a + * BufferedReader. + *

    + * Character encoding names can be found at + * IANA. + *

    + * Due to the implementation of OutputStreamWriter, this method performs a + * flush. + *

    + * This method uses {@link java.io.OutputStreamWriter}. + * + * @param input the Reader to read from + * @param output the OutputStream to write to + * @param encoding the encoding to use, null means platform default + * @throws NullPointerException if the input or output is null + * @throws java.io.IOException if an I/O error occurs + * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io.UnsupportedEncodingException} in version 2.2 if the encoding is not + * supported. + * @since 1.1 + */ + public static void copy(Reader input, OutputStream output, String encoding) throws IOException { + copy(input, output, Charsets.toCharset(encoding)); + } + + // content equals + //----------------------------------------------------------------------- + + /** + * Compare the contents of two Streams to determine if they are equal or + * not. + *

    + * This method buffers the input internally using + * BufferedInputStream if they are not already buffered. + * + * @param input1 the first stream + * @param input2 the second stream + * @return true if the content of the streams are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws java.io.IOException if an I/O error occurs + */ + public static boolean contentEquals(InputStream input1, InputStream input2) + throws IOException { + if (!(input1 instanceof BufferedInputStream)) { + input1 = new BufferedInputStream(input1); + } + if (!(input2 instanceof BufferedInputStream)) { + input2 = new BufferedInputStream(input2); + } + + int ch = input1.read(); + while (EOF != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == EOF; + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not. + *

    + * This method buffers the input internally using + * BufferedReader if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal or they both don't + * exist, false otherwise + * @throws NullPointerException if either input is null + * @throws java.io.IOException if an I/O error occurs + * @since 1.1 + */ + public static boolean contentEquals(Reader input1, Reader input2) + throws IOException { + + input1 = toBufferedReader(input1); + input2 = toBufferedReader(input2); + + int ch = input1.read(); + while (EOF != ch) { + int ch2 = input2.read(); + if (ch != ch2) { + return false; + } + ch = input1.read(); + } + + int ch2 = input2.read(); + return ch2 == EOF; + } + + /** + * Compare the contents of two Readers to determine if they are equal or + * not, ignoring EOL characters. + *

    + * This method buffers the input internally using + * BufferedReader if they are not already buffered. + * + * @param input1 the first reader + * @param input2 the second reader + * @return true if the content of the readers are equal (ignoring EOL differences), false otherwise + * @throws NullPointerException if either input is null + * @throws java.io.IOException if an I/O error occurs + * @since 2.2 + */ + public static boolean contentEqualsIgnoreEOL(Reader input1, Reader input2) + throws IOException { + BufferedReader br1 = toBufferedReader(input1); + BufferedReader br2 = toBufferedReader(input2); + + String line1 = br1.readLine(); + String line2 = br2.readLine(); + while (line1 != null && line2 != null && line1.equals(line2)) { + line1 = br1.readLine(); + line2 = br2.readLine(); + } + return line1 == null ? line2 == null ? true : false : line1.equals(line2); + } + + /** + * Skip bytes from an input byte stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input byte stream to skip + * @param toSkip number of bytes to skip. + * @return number of bytes actually skipped. + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see java.io.InputStream#skip(long) + * @since 2.0 + */ + public static long skip(InputStream input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_BYTE_BUFFER == null) { + SKIP_BYTE_BUFFER = new byte[SKIP_BUFFER_SIZE]; + } + long remain = toSkip; + while (remain > 0) { + long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skip characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input character stream to skip + * @param toSkip number of characters to skip. + * @return number of characters actually skipped. + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @see java.io.Reader#skip(long) + * @since 2.0 + */ + public static long skip(Reader input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Skip count must be non-negative, actual: " + toSkip); + } + /* + * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data + * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer + * size were variable, we would need to synch. to ensure some other thread did not create a smaller one) + */ + if (SKIP_CHAR_BUFFER == null) { + SKIP_CHAR_BUFFER = new char[SKIP_BUFFER_SIZE]; + } + long remain = toSkip; + while (remain > 0) { + long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BUFFER_SIZE)); + if (n < 0) { // EOF + break; + } + remain -= n; + } + return toSkip - remain; + } + + /** + * Skip the requested number of bytes or fail if there are not enough left. + *

    + * This allows for the possibility that {@link java.io.InputStream#skip(long)} may + * not skip as many bytes as requested (most likely because of reaching EOF). + * + * @param input stream to skip + * @param toSkip the number of bytes to skip + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws java.io.EOFException if the number of bytes skipped was incorrect + * @see java.io.InputStream#skip(long) + * @since 2.0 + */ + public static void skipFully(InputStream input, long toSkip) throws IOException { + if (toSkip < 0) { + throw new IllegalArgumentException("Bytes to skip must not be negative: " + toSkip); + } + long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Bytes to skip: " + toSkip + " actual: " + skipped); + } + } + + /** + * Skip the requested number of characters or fail if there are not enough left. + *

    + * This allows for the possibility that {@link java.io.Reader#skip(long)} may + * not skip as many characters as requested (most likely because of reaching EOF). + * + * @param input stream to skip + * @param toSkip the number of characters to skip + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if toSkip is negative + * @throws java.io.EOFException if the number of characters skipped was incorrect + * @see java.io.Reader#skip(long) + * @since 2.0 + */ + public static void skipFully(Reader input, long toSkip) throws IOException { + long skipped = skip(input, toSkip); + if (skipped != toSkip) { + throw new EOFException("Chars to skip: " + toSkip + " actual: " + skipped); + } + } + + + /** + * Read characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(Reader input, char[] buffer, int offset, int length) throws IOException { + if (length < 0) { + throw new IllegalArgumentException("Length must not be negative: " + length); + } + int remaining = length; + while (remaining > 0) { + int location = length - remaining; + int count = input.read(buffer, offset + location, remaining); + if (EOF == count) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Read characters from an input character stream. + * This implementation guarantees that it will read as many characters + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.Reader}. + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(Reader input, char[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read bytes from an input stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.InputStream}. + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(InputStream input, byte[] buffer, int offset, int length) throws IOException { + if (length < 0) { + throw new IllegalArgumentException("Length must not be negative: " + length); + } + int remaining = length; + while (remaining > 0) { + int location = length - remaining; + int count = input.read(buffer, offset + location, remaining); + if (EOF == count) { // EOF + break; + } + remaining -= count; + } + return length - remaining; + } + + /** + * Read bytes from an input stream. + * This implementation guarantees that it will read as many bytes + * as possible before giving up; this may not always be the case for + * subclasses of {@link java.io.InputStream}. + * + * @param input where to read input from + * @param buffer destination + * @return actual length read; may be less than requested if EOF was reached + * @throws java.io.IOException if a read error occurs + * @since 2.2 + */ + public static int read(InputStream input, byte[] buffer) throws IOException { + return read(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of characters or fail if there are not enough left. + *

    + * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may + * not read as many characters as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of characters read was incorrect + * @since 2.2 + */ + public static void readFully(Reader input, char[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Length to read: " + length + " actual: " + actual); + } + } + + /** + * Read the requested number of characters or fail if there are not enough left. + *

    + * This allows for the possibility that {@link java.io.Reader#read(char[], int, int)} may + * not read as many characters as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of characters read was incorrect + * @since 2.2 + */ + public static void readFully(Reader input, char[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + *

    + * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may + * not read as many bytes as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @param offset inital offset into buffer + * @param length length to read, must be >= 0 + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of bytes read was incorrect + * @since 2.2 + */ + public static void readFully(InputStream input, byte[] buffer, int offset, int length) throws IOException { + int actual = read(input, buffer, offset, length); + if (actual != length) { + throw new EOFException("Length to read: " + length + " actual: " + actual); + } + } + + /** + * Read the requested number of bytes or fail if there are not enough left. + *

    + * This allows for the possibility that {@link java.io.InputStream#read(byte[], int, int)} may + * not read as many bytes as requested (most likely because of reaching EOF). + * + * @param input where to read input from + * @param buffer destination + * @throws java.io.IOException if there is a problem reading the file + * @throws IllegalArgumentException if length is negative + * @throws java.io.EOFException if the number of bytes read was incorrect + * @since 2.2 + */ + public static void readFully(InputStream input, byte[] buffer) throws IOException { + readFully(input, buffer, 0, buffer.length); + } +} diff --git a/app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java b/app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java index 6202023..0e9e47b 100644 --- a/app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java +++ b/app/src/main/java/com/litesuits/common/io/stream/ByteArrayOutputStream.java @@ -1,357 +1,357 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io.stream; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.SequenceInputStream; -import java.io.UnsupportedEncodingException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * This class implements an output stream in which the data is - * written into a byte array. The buffer automatically grows as data - * is written to it. - *

    - * The data can be retrieved using toByteArray() and - * toString(). - *

    - * Closing a ByteArrayOutputStream has no effect. The methods in - * this class can be called after the stream has been closed without - * generating an IOException. - *

    - * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} - * class. The original implementation only allocates 32 bytes at the beginning. - * As this class is designed for heavy duty it starts at 1024 bytes. In contrast - * to the original it doesn't reallocate the whole memory block but allocates - * additional buffers. This way no buffers need to be garbage collected and - * the contents don't have to be copied to the new buffer. This class is - * designed to behave exactly like the original. The only exception is the - * deprecated toString(int) method that has been ignored. - * - * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $ - */ -public class ByteArrayOutputStream extends OutputStream { - - /** A singleton empty byte array. */ - private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; - - /** The list of buffers, which grows and never reduces. */ - private final List buffers = new ArrayList(); - /** The index of the current buffer. */ - private int currentBufferIndex; - /** The total count of bytes in all the filled buffers. */ - private int filledBufferSum; - /** The current buffer. */ - private byte[] currentBuffer; - /** The total count of bytes written. */ - private int count; - - /** - * Creates a new byte array output stream. The buffer capacity is - * initially 1024 bytes, though its size increases if necessary. - */ - public ByteArrayOutputStream() { - this(1024); - } - - /** - * Creates a new byte array output stream, with a buffer capacity of - * the specified size, in bytes. - * - * @param size the initial size - * @throws IllegalArgumentException if size is negative - */ - public ByteArrayOutputStream(int size) { - if (size < 0) { - throw new IllegalArgumentException( - "Negative initial size: " + size); - } - synchronized (this) { - needNewBuffer(size); - } - } - - /** - * Makes a new buffer available either by allocating - * a new one or re-cycling an existing one. - * - * @param newcount the size of the buffer if one is created - */ - private void needNewBuffer(int newcount) { - if (currentBufferIndex < buffers.size() - 1) { - //Recycling old buffer - filledBufferSum += currentBuffer.length; - - currentBufferIndex++; - currentBuffer = buffers.get(currentBufferIndex); - } else { - //Creating new buffer - int newBufferSize; - if (currentBuffer == null) { - newBufferSize = newcount; - filledBufferSum = 0; - } else { - newBufferSize = Math.max( - currentBuffer.length << 1, - newcount - filledBufferSum); - filledBufferSum += currentBuffer.length; - } - - currentBufferIndex++; - currentBuffer = new byte[newBufferSize]; - buffers.add(currentBuffer); - } - } - - /** - * Write the bytes to byte array. - * @param b the bytes to write - * @param off The start offset - * @param len The number of bytes to write - */ - @Override - public void write(byte[] b, int off, int len) { - if ((off < 0) - || (off > b.length) - || (len < 0) - || ((off + len) > b.length) - || ((off + len) < 0)) { - throw new IndexOutOfBoundsException(); - } else if (len == 0) { - return; - } - synchronized (this) { - int newcount = count + len; - int remaining = len; - int inBufferPos = count - filledBufferSum; - while (remaining > 0) { - int part = Math.min(remaining, currentBuffer.length - inBufferPos); - System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); - remaining -= part; - if (remaining > 0) { - needNewBuffer(newcount); - inBufferPos = 0; - } - } - count = newcount; - } - } - - /** - * Write a byte to byte array. - * @param b the byte to write - */ - @Override - public synchronized void write(int b) { - int inBufferPos = count - filledBufferSum; - if (inBufferPos == currentBuffer.length) { - needNewBuffer(count + 1); - inBufferPos = 0; - } - currentBuffer[inBufferPos] = (byte) b; - count++; - } - - /** - * Writes the entire contents of the specified input stream to this - * byte stream. Bytes from the input stream are read directly into the - * internal buffers of this streams. - * - * @param in the input stream to read from - * @return total number of bytes read from the input stream - * (and written to this stream) - * @throws java.io.IOException if an I/O error occurs while reading the input stream - * @since 1.4 - */ - public synchronized int write(InputStream in) throws IOException { - int readCount = 0; - int inBufferPos = count - filledBufferSum; - int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); - while (n != -1) { - readCount += n; - inBufferPos += n; - count += n; - if (inBufferPos == currentBuffer.length) { - needNewBuffer(currentBuffer.length); - inBufferPos = 0; - } - n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); - } - return readCount; - } - - /** - * Return the current size of the byte array. - * @return the current size of the byte array - */ - public synchronized int size() { - return count; - } - - /** - * Closing a ByteArrayOutputStream has no effect. The methods in - * this class can be called after the stream has been closed without - * generating an IOException. - * - * @throws java.io.IOException never (this method should not declare this exception - * but it has to now due to backwards compatability) - */ - @Override - public void close() throws IOException { - //nop - } - - /** - * @see java.io.ByteArrayOutputStream#reset() - */ - public synchronized void reset() { - count = 0; - filledBufferSum = 0; - currentBufferIndex = 0; - currentBuffer = buffers.get(currentBufferIndex); - } - - /** - * Writes the entire contents of this byte stream to the - * specified output stream. - * - * @param out the output stream to write to - * @throws java.io.IOException if an I/O error occurs, such as if the stream is closed - * @see java.io.ByteArrayOutputStream#writeTo(java.io.OutputStream) - */ - public synchronized void writeTo(OutputStream out) throws IOException { - int remaining = count; - for (byte[] buf : buffers) { - int c = Math.min(buf.length, remaining); - out.write(buf, 0, c); - remaining -= c; - if (remaining == 0) { - break; - } - } - } - - /** - * Fetches entire contents of an InputStream and represent - * same data as result InputStream. - *

    - * This method is useful where, - *

      - *
    • Source InputStream is slow.
    • - *
    • It has network resources associated, so we cannot keep it open for - * long time.
    • - *
    • It has network timeout associated.
    • - *
    - * It can be used in favor of {@link #toByteArray()}, since it - * avoids unnecessary allocation and copy of byte[].
    - * This method buffers the input internally, so there is no need to use a - * BufferedInputStream. - * - * @param input Stream to be fully buffered. - * @return A fully buffered stream. - * @throws java.io.IOException if an I/O error occurs - * @since 2.0 - */ - public static InputStream toBufferedInputStream(InputStream input) - throws IOException { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - output.write(input); - return output.toBufferedInputStream(); - } - - /** - * Gets the current contents of this byte stream as a Input Stream. The - * returned stream is backed by buffers of this stream, - * avoiding memory allocation and copy, thus saving space and time.
    - * - * @return the current contents of this output stream. - * @see java.io.ByteArrayOutputStream#toByteArray() - * @see #reset() - * @since 2.0 - */ - private InputStream toBufferedInputStream() { - int remaining = count; - if (remaining == 0) { - return new ClosedInputStream(); - } - List list = new ArrayList(buffers.size()); - for (byte[] buf : buffers) { - int c = Math.min(buf.length, remaining); - list.add(new ByteArrayInputStream(buf, 0, c)); - remaining -= c; - if (remaining == 0) { - break; - } - } - return new SequenceInputStream(Collections.enumeration(list)); - } - - /** - * Gets the curent contents of this byte stream as a byte array. - * The result is independent of this stream. - * - * @return the current contents of this output stream, as a byte array - * @see java.io.ByteArrayOutputStream#toByteArray() - */ - public synchronized byte[] toByteArray() { - int remaining = count; - if (remaining == 0) { - return EMPTY_BYTE_ARRAY; - } - byte newbuf[] = new byte[remaining]; - int pos = 0; - for (byte[] buf : buffers) { - int c = Math.min(buf.length, remaining); - System.arraycopy(buf, 0, newbuf, pos, c); - pos += c; - remaining -= c; - if (remaining == 0) { - break; - } - } - return newbuf; - } - - /** - * Gets the curent contents of this byte stream as a string. - * @return the contents of the byte array as a String - * @see java.io.ByteArrayOutputStream#toString() - */ - @Override - public String toString() { - return new String(toByteArray()); - } - - /** - * Gets the curent contents of this byte stream as a string - * using the specified encoding. - * - * @param enc the name of the character encoding - * @return the string converted from the byte array - * @throws java.io.UnsupportedEncodingException if the encoding is not supported - * @see java.io.ByteArrayOutputStream#toString(String) - */ - public String toString(String enc) throws UnsupportedEncodingException { - return new String(toByteArray(), enc); - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io.stream; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.SequenceInputStream; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class implements an output stream in which the data is + * written into a byte array. The buffer automatically grows as data + * is written to it. + *

    + * The data can be retrieved using toByteArray() and + * toString(). + *

    + * Closing a ByteArrayOutputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + *

    + * This is an alternative implementation of the {@link java.io.ByteArrayOutputStream} + * class. The original implementation only allocates 32 bytes at the beginning. + * As this class is designed for heavy duty it starts at 1024 bytes. In contrast + * to the original it doesn't reallocate the whole memory block but allocates + * additional buffers. This way no buffers need to be garbage collected and + * the contents don't have to be copied to the new buffer. This class is + * designed to behave exactly like the original. The only exception is the + * deprecated toString(int) method that has been ignored. + * + * @version $Id: ByteArrayOutputStream.java 1304052 2012-03-22 20:55:29Z ggregory $ + */ +public class ByteArrayOutputStream extends OutputStream { + + /** A singleton empty byte array. */ + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + + /** The list of buffers, which grows and never reduces. */ + private final List buffers = new ArrayList(); + /** The index of the current buffer. */ + private int currentBufferIndex; + /** The total count of bytes in all the filled buffers. */ + private int filledBufferSum; + /** The current buffer. */ + private byte[] currentBuffer; + /** The total count of bytes written. */ + private int count; + + /** + * Creates a new byte array output stream. The buffer capacity is + * initially 1024 bytes, though its size increases if necessary. + */ + public ByteArrayOutputStream() { + this(1024); + } + + /** + * Creates a new byte array output stream, with a buffer capacity of + * the specified size, in bytes. + * + * @param size the initial size + * @throws IllegalArgumentException if size is negative + */ + public ByteArrayOutputStream(int size) { + if (size < 0) { + throw new IllegalArgumentException( + "Negative initial size: " + size); + } + synchronized (this) { + needNewBuffer(size); + } + } + + /** + * Makes a new buffer available either by allocating + * a new one or re-cycling an existing one. + * + * @param newcount the size of the buffer if one is created + */ + private void needNewBuffer(int newcount) { + if (currentBufferIndex < buffers.size() - 1) { + //Recycling old buffer + filledBufferSum += currentBuffer.length; + + currentBufferIndex++; + currentBuffer = buffers.get(currentBufferIndex); + } else { + //Creating new buffer + int newBufferSize; + if (currentBuffer == null) { + newBufferSize = newcount; + filledBufferSum = 0; + } else { + newBufferSize = Math.max( + currentBuffer.length << 1, + newcount - filledBufferSum); + filledBufferSum += currentBuffer.length; + } + + currentBufferIndex++; + currentBuffer = new byte[newBufferSize]; + buffers.add(currentBuffer); + } + } + + /** + * Write the bytes to byte array. + * @param b the bytes to write + * @param off The start offset + * @param len The number of bytes to write + */ + @Override + public void write(byte[] b, int off, int len) { + if ((off < 0) + || (off > b.length) + || (len < 0) + || ((off + len) > b.length) + || ((off + len) < 0)) { + throw new IndexOutOfBoundsException(); + } else if (len == 0) { + return; + } + synchronized (this) { + int newcount = count + len; + int remaining = len; + int inBufferPos = count - filledBufferSum; + while (remaining > 0) { + int part = Math.min(remaining, currentBuffer.length - inBufferPos); + System.arraycopy(b, off + len - remaining, currentBuffer, inBufferPos, part); + remaining -= part; + if (remaining > 0) { + needNewBuffer(newcount); + inBufferPos = 0; + } + } + count = newcount; + } + } + + /** + * Write a byte to byte array. + * @param b the byte to write + */ + @Override + public synchronized void write(int b) { + int inBufferPos = count - filledBufferSum; + if (inBufferPos == currentBuffer.length) { + needNewBuffer(count + 1); + inBufferPos = 0; + } + currentBuffer[inBufferPos] = (byte) b; + count++; + } + + /** + * Writes the entire contents of the specified input stream to this + * byte stream. Bytes from the input stream are read directly into the + * internal buffers of this streams. + * + * @param in the input stream to read from + * @return total number of bytes read from the input stream + * (and written to this stream) + * @throws java.io.IOException if an I/O error occurs while reading the input stream + * @since 1.4 + */ + public synchronized int write(InputStream in) throws IOException { + int readCount = 0; + int inBufferPos = count - filledBufferSum; + int n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); + while (n != -1) { + readCount += n; + inBufferPos += n; + count += n; + if (inBufferPos == currentBuffer.length) { + needNewBuffer(currentBuffer.length); + inBufferPos = 0; + } + n = in.read(currentBuffer, inBufferPos, currentBuffer.length - inBufferPos); + } + return readCount; + } + + /** + * Return the current size of the byte array. + * @return the current size of the byte array + */ + public synchronized int size() { + return count; + } + + /** + * Closing a ByteArrayOutputStream has no effect. The methods in + * this class can be called after the stream has been closed without + * generating an IOException. + * + * @throws java.io.IOException never (this method should not declare this exception + * but it has to now due to backwards compatability) + */ + @Override + public void close() throws IOException { + //nop + } + + /** + * @see java.io.ByteArrayOutputStream#reset() + */ + public synchronized void reset() { + count = 0; + filledBufferSum = 0; + currentBufferIndex = 0; + currentBuffer = buffers.get(currentBufferIndex); + } + + /** + * Writes the entire contents of this byte stream to the + * specified output stream. + * + * @param out the output stream to write to + * @throws java.io.IOException if an I/O error occurs, such as if the stream is closed + * @see java.io.ByteArrayOutputStream#writeTo(java.io.OutputStream) + */ + public synchronized void writeTo(OutputStream out) throws IOException { + int remaining = count; + for (byte[] buf : buffers) { + int c = Math.min(buf.length, remaining); + out.write(buf, 0, c); + remaining -= c; + if (remaining == 0) { + break; + } + } + } + + /** + * Fetches entire contents of an InputStream and represent + * same data as result InputStream. + *

    + * This method is useful where, + *

      + *
    • Source InputStream is slow.
    • + *
    • It has network resources associated, so we cannot keep it open for + * long time.
    • + *
    • It has network timeout associated.
    • + *
    + * It can be used in favor of {@link #toByteArray()}, since it + * avoids unnecessary allocation and copy of byte[].
    + * This method buffers the input internally, so there is no need to use a + * BufferedInputStream. + * + * @param input Stream to be fully buffered. + * @return A fully buffered stream. + * @throws java.io.IOException if an I/O error occurs + * @since 2.0 + */ + public static InputStream toBufferedInputStream(InputStream input) + throws IOException { + ByteArrayOutputStream output = new ByteArrayOutputStream(); + output.write(input); + return output.toBufferedInputStream(); + } + + /** + * Gets the current contents of this byte stream as a Input Stream. The + * returned stream is backed by buffers of this stream, + * avoiding memory allocation and copy, thus saving space and time.
    + * + * @return the current contents of this output stream. + * @see java.io.ByteArrayOutputStream#toByteArray() + * @see #reset() + * @since 2.0 + */ + private InputStream toBufferedInputStream() { + int remaining = count; + if (remaining == 0) { + return new ClosedInputStream(); + } + List list = new ArrayList(buffers.size()); + for (byte[] buf : buffers) { + int c = Math.min(buf.length, remaining); + list.add(new ByteArrayInputStream(buf, 0, c)); + remaining -= c; + if (remaining == 0) { + break; + } + } + return new SequenceInputStream(Collections.enumeration(list)); + } + + /** + * Gets the curent contents of this byte stream as a byte array. + * The result is independent of this stream. + * + * @return the current contents of this output stream, as a byte array + * @see java.io.ByteArrayOutputStream#toByteArray() + */ + public synchronized byte[] toByteArray() { + int remaining = count; + if (remaining == 0) { + return EMPTY_BYTE_ARRAY; + } + byte newbuf[] = new byte[remaining]; + int pos = 0; + for (byte[] buf : buffers) { + int c = Math.min(buf.length, remaining); + System.arraycopy(buf, 0, newbuf, pos, c); + pos += c; + remaining -= c; + if (remaining == 0) { + break; + } + } + return newbuf; + } + + /** + * Gets the curent contents of this byte stream as a string. + * @return the contents of the byte array as a String + * @see java.io.ByteArrayOutputStream#toString() + */ + @Override + public String toString() { + return new String(toByteArray()); + } + + /** + * Gets the curent contents of this byte stream as a string + * using the specified encoding. + * + * @param enc the name of the character encoding + * @return the string converted from the byte array + * @throws java.io.UnsupportedEncodingException if the encoding is not supported + * @see java.io.ByteArrayOutputStream#toString(String) + */ + public String toString(String enc) throws UnsupportedEncodingException { + return new String(toByteArray(), enc); + } + +} diff --git a/app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java b/app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java index 0739e79..a44a029 100644 --- a/app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java +++ b/app/src/main/java/com/litesuits/common/io/stream/ClosedInputStream.java @@ -1,49 +1,49 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io.stream; - -import java.io.InputStream; - -/** - * Closed input stream. This stream returns -1 to all attempts to read - * something from the stream. - *

    - * Typically uses of this class include testing for corner cases in methods - * that accept input streams and acting as a sentinel value instead of a - * {@code null} input stream. - * - * @version $Id: ClosedInputStream.java 1307459 2012-03-30 15:11:44Z ggregory $ - * @since 1.4 - */ -public class ClosedInputStream extends InputStream { - - /** - * A singleton. - */ - public static final ClosedInputStream CLOSED_INPUT_STREAM = new ClosedInputStream(); - - /** - * Returns -1 to indicate that the stream is closed. - * - * @return always -1 - */ - @Override - public int read() { - return -1; - } - -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io.stream; + +import java.io.InputStream; + +/** + * Closed input stream. This stream returns -1 to all attempts to read + * something from the stream. + *

    + * Typically uses of this class include testing for corner cases in methods + * that accept input streams and acting as a sentinel value instead of a + * {@code null} input stream. + * + * @version $Id: ClosedInputStream.java 1307459 2012-03-30 15:11:44Z ggregory $ + * @since 1.4 + */ +public class ClosedInputStream extends InputStream { + + /** + * A singleton. + */ + public static final ClosedInputStream CLOSED_INPUT_STREAM = new ClosedInputStream(); + + /** + * Returns -1 to indicate that the stream is closed. + * + * @return always -1 + */ + @Override + public int read() { + return -1; + } + +} diff --git a/app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java b/app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java index 99e148a..2016117 100644 --- a/app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java +++ b/app/src/main/java/com/litesuits/common/io/stream/StringBuilderWriter.java @@ -1,160 +1,160 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.litesuits.common.io.stream; - -import java.io.Serializable; -import java.io.Writer; - -/** - * {@link java.io.Writer} implementation that outputs to a {@link StringBuilder}. - *

    - * NOTE: This implementation, as an alternative to - * java.io.StringWriter, provides an un-synchronized - * (i.e. for use in a single thread) implementation for better performance. - * For safe usage with multiple {@link Thread}s then - * java.io.StringWriter should be used. - * - * @version $Id: StringBuilderWriter.java 1304052 2012-03-22 20:55:29Z ggregory $ - * @since 2.0 - */ -public class StringBuilderWriter extends Writer implements Serializable { - - private final StringBuilder builder; - - /** - * Construct a new {@link StringBuilder} instance with default capacity. - */ - public StringBuilderWriter() { - this.builder = new StringBuilder(); - } - - /** - * Construct a new {@link StringBuilder} instance with the specified capacity. - * - * @param capacity The initial capacity of the underlying {@link StringBuilder} - */ - public StringBuilderWriter(int capacity) { - this.builder = new StringBuilder(capacity); - } - - /** - * Construct a new instance with the specified {@link StringBuilder}. - * - * @param builder The String builder - */ - public StringBuilderWriter(StringBuilder builder) { - this.builder = builder != null ? builder : new StringBuilder(); - } - - /** - * Append a single character to this Writer. - * - * @param value The character to append - * @return This writer instance - */ - @Override - public Writer append(char value) { - builder.append(value); - return this; - } - - /** - * Append a character sequence to this Writer. - * - * @param value The character to append - * @return This writer instance - */ - @Override - public Writer append(CharSequence value) { - builder.append(value); - return this; - } - - /** - * Append a portion of a character sequence to the {@link StringBuilder}. - * - * @param value The character to append - * @param start The index of the first character - * @param end The index of the last character + 1 - * @return This writer instance - */ - @Override - public Writer append(CharSequence value, int start, int end) { - builder.append(value, start, end); - return this; - } - - /** - * Closing this writer has no effect. - */ - @Override - public void close() { - } - - /** - * Flushing this writer has no effect. - */ - @Override - public void flush() { - } - - - /** - * Write a String to the {@link StringBuilder}. - * - * @param value The value to write - */ - @Override - public void write(String value) { - if (value != null) { - builder.append(value); - } - } - - /** - * Write a portion of a character array to the {@link StringBuilder}. - * - * @param value The value to write - * @param offset The index of the first character - * @param length The number of characters to write - */ - @Override - public void write(char[] value, int offset, int length) { - if (value != null) { - builder.append(value, offset, length); - } - } - - /** - * Return the underlying builder. - * - * @return The underlying builder - */ - public StringBuilder getBuilder() { - return builder; - } - - /** - * Returns {@link StringBuilder#toString()}. - * - * @return The contents of the String builder. - */ - @Override - public String toString() { - return builder.toString(); - } -} +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.litesuits.common.io.stream; + +import java.io.Serializable; +import java.io.Writer; + +/** + * {@link java.io.Writer} implementation that outputs to a {@link StringBuilder}. + *

    + * NOTE: This implementation, as an alternative to + * java.io.StringWriter, provides an un-synchronized + * (i.e. for use in a single thread) implementation for better performance. + * For safe usage with multiple {@link Thread}s then + * java.io.StringWriter should be used. + * + * @version $Id: StringBuilderWriter.java 1304052 2012-03-22 20:55:29Z ggregory $ + * @since 2.0 + */ +public class StringBuilderWriter extends Writer implements Serializable { + + private final StringBuilder builder; + + /** + * Construct a new {@link StringBuilder} instance with default capacity. + */ + public StringBuilderWriter() { + this.builder = new StringBuilder(); + } + + /** + * Construct a new {@link StringBuilder} instance with the specified capacity. + * + * @param capacity The initial capacity of the underlying {@link StringBuilder} + */ + public StringBuilderWriter(int capacity) { + this.builder = new StringBuilder(capacity); + } + + /** + * Construct a new instance with the specified {@link StringBuilder}. + * + * @param builder The String builder + */ + public StringBuilderWriter(StringBuilder builder) { + this.builder = builder != null ? builder : new StringBuilder(); + } + + /** + * Append a single character to this Writer. + * + * @param value The character to append + * @return This writer instance + */ + @Override + public Writer append(char value) { + builder.append(value); + return this; + } + + /** + * Append a character sequence to this Writer. + * + * @param value The character to append + * @return This writer instance + */ + @Override + public Writer append(CharSequence value) { + builder.append(value); + return this; + } + + /** + * Append a portion of a character sequence to the {@link StringBuilder}. + * + * @param value The character to append + * @param start The index of the first character + * @param end The index of the last character + 1 + * @return This writer instance + */ + @Override + public Writer append(CharSequence value, int start, int end) { + builder.append(value, start, end); + return this; + } + + /** + * Closing this writer has no effect. + */ + @Override + public void close() { + } + + /** + * Flushing this writer has no effect. + */ + @Override + public void flush() { + } + + + /** + * Write a String to the {@link StringBuilder}. + * + * @param value The value to write + */ + @Override + public void write(String value) { + if (value != null) { + builder.append(value); + } + } + + /** + * Write a portion of a character array to the {@link StringBuilder}. + * + * @param value The value to write + * @param offset The index of the first character + * @param length The number of characters to write + */ + @Override + public void write(char[] value, int offset, int length) { + if (value != null) { + builder.append(value, offset, length); + } + } + + /** + * Return the underlying builder. + * + * @return The underlying builder + */ + public StringBuilder getBuilder() { + return builder; + } + + /** + * Returns {@link StringBuilder#toString()}. + * + * @return The contents of the String builder. + */ + @Override + public String toString() { + return builder.toString(); + } +} diff --git a/app/src/main/java/com/litesuits/common/utils/ClassUtil.java b/app/src/main/java/com/litesuits/common/utils/ClassUtil.java index bcbd760..6742f77 100755 --- a/app/src/main/java/com/litesuits/common/utils/ClassUtil.java +++ b/app/src/main/java/com/litesuits/common/utils/ClassUtil.java @@ -1,71 +1,71 @@ -package com.litesuits.common.utils; - -import java.lang.reflect.Constructor; -import java.util.Collection; -import java.util.Date; - -/** - * 类工具 - * - * @author mty - * @date 2013-6-10下午8:00:46 - */ -public class ClassUtil { - - /** - * 判断类是否是基础数据类型 - * 目前支持11种 - * - * @param clazz - * @return - */ - public static boolean isBaseDataType(Class clazz) { - return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Boolean.class) - || clazz.equals(Integer.class) || clazz.equals(Long.class) || clazz.equals(Float.class) - || clazz.equals(Double.class) || clazz.equals(Byte.class) || clazz.equals(Character.class) - || clazz.equals(Short.class) || clazz.equals(Date.class) || clazz.equals(byte[].class) - || clazz.equals(Byte[].class); - } - - /** - * 根据类获取对象:不再必须一个无参构造 - * - * @param claxx - * @return - * @throws Exception - */ - public static T newInstance(Class claxx) throws Exception { - Constructor[] cons = claxx.getDeclaredConstructors(); - for (Constructor c : cons) { - Class[] cls = c.getParameterTypes(); - if (cls.length == 0) { - c.setAccessible(true); - return (T) c.newInstance(); - } else { - Object[] objs = new Object[cls.length]; - for (int i = 0; i < cls.length; i++) { - objs[i] = getDefaultPrimiticeValue(cls[i]); - } - c.setAccessible(true); - return (T) c.newInstance(objs); - } - } - return null; - } - - public static Object getDefaultPrimiticeValue(Class clazz) { - if (clazz.isPrimitive()) { - return clazz == boolean.class ? false : 0; - } - return null; - } - - public static boolean isCollection(Class claxx) { - return Collection.class.isAssignableFrom(claxx); - } - - public static boolean isArray(Class claxx) { - return claxx.isArray(); - } - -} +package com.litesuits.common.utils; + +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Date; + +/** + * 类工具 + * + * @author mty + * @date 2013-6-10下午8:00:46 + */ +public class ClassUtil { + + /** + * 判断类是否是基础数据类型 + * 目前支持11种 + * + * @param clazz + * @return + */ + public static boolean isBaseDataType(Class clazz) { + return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Boolean.class) + || clazz.equals(Integer.class) || clazz.equals(Long.class) || clazz.equals(Float.class) + || clazz.equals(Double.class) || clazz.equals(Byte.class) || clazz.equals(Character.class) + || clazz.equals(Short.class) || clazz.equals(Date.class) || clazz.equals(byte[].class) + || clazz.equals(Byte[].class); + } + + /** + * 根据类获取对象:不再必须一个无参构造 + * + * @param claxx + * @return + * @throws Exception + */ + public static T newInstance(Class claxx) throws Exception { + Constructor[] cons = claxx.getDeclaredConstructors(); + for (Constructor c : cons) { + Class[] cls = c.getParameterTypes(); + if (cls.length == 0) { + c.setAccessible(true); + return (T) c.newInstance(); + } else { + Object[] objs = new Object[cls.length]; + for (int i = 0; i < cls.length; i++) { + objs[i] = getDefaultPrimiticeValue(cls[i]); + } + c.setAccessible(true); + return (T) c.newInstance(objs); + } + } + return null; + } + + public static Object getDefaultPrimiticeValue(Class clazz) { + if (clazz.isPrimitive()) { + return clazz == boolean.class ? false : 0; + } + return null; + } + + public static boolean isCollection(Class claxx) { + return Collection.class.isAssignableFrom(claxx); + } + + public static boolean isArray(Class claxx) { + return claxx.isArray(); + } + +} diff --git a/app/src/main/java/com/litesuits/common/utils/FieldUtil.java b/app/src/main/java/com/litesuits/common/utils/FieldUtil.java index dedd0cf..ddd77d6 100755 --- a/app/src/main/java/com/litesuits/common/utils/FieldUtil.java +++ b/app/src/main/java/com/litesuits/common/utils/FieldUtil.java @@ -1,128 +1,128 @@ -package com.litesuits.common.utils; - -import java.io.Serializable; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.LinkedList; -import java.util.List; - -/** - * 域工具 - * - * @author mty - * @date 2013-6-10下午6:36:29 - */ -public class FieldUtil { - - /** - * 判断是否序列化 - * - * @param f - * @return - */ - public static boolean isSerializable(Field f) { - Class[] cls = f.getType().getInterfaces(); - for (Class c : cls) { - if (Serializable.class == c) { - return true; - } - } - return false; - } - - /** - * 设置域的值 - * - * @param f - * @param obj - * @return - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - public static Object set(Field f, Object obj, Object value) throws IllegalArgumentException, IllegalAccessException { - f.setAccessible(true); - f.set(obj, value); - return f.get(obj); - } - - /** - * 获取域的值 - * - * @param f - * @param obj - * @return - * @throws IllegalAccessException - * @throws IllegalArgumentException - */ - public static Object get(Field f, Object obj) throws IllegalArgumentException, IllegalAccessException { - f.setAccessible(true); - return f.get(obj); - } - - public static boolean isLong(Field field) { - return field.getType() == long.class || field.getType() == Long.class; - } - - public static boolean isInteger(Field field) { - return field.getType() == int.class || field.getType() != Integer.class; - } - - /** - * 获取域的泛型类型,如果不带泛型返回null - * - * @param f - * @return - */ - public static Class getGenericType(Field f) { - Type type = f.getGenericType(); - if (type instanceof ParameterizedType) { - type = ((ParameterizedType) type).getActualTypeArguments()[0]; - if (type instanceof Class) return (Class) type; - } else if (type instanceof Class) return (Class) type; - return null; - } - - /** - * 获取数组的类型 - * - * @param f - * @return - */ - public static Class getComponentType(Field f) { - return f.getType().getComponentType(); - } - - /** - * 获取全部Field,包括父类 - * - * @param claxx - * @return - */ - public static List getAllDeclaredFields(Class claxx) { - // find all field. - LinkedList fieldList = new LinkedList(); - while (claxx != null && claxx != Object.class) { - Field[] fs = claxx.getDeclaredFields(); - for (int i = 0; i < fs.length; i++) { - Field f = fs[i]; - if (!isInvalid(f)) { - fieldList.addLast(f); - } - } - claxx = claxx.getSuperclass(); - } - return fieldList; - } - - /** - * 是静态常量或者内部结构属性 - * - * @param f - * @return - */ - public static boolean isInvalid(Field f) { - return (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) || f.isSynthetic(); - } -} +package com.litesuits.common.utils; + +import java.io.Serializable; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.LinkedList; +import java.util.List; + +/** + * 域工具 + * + * @author mty + * @date 2013-6-10下午6:36:29 + */ +public class FieldUtil { + + /** + * 判断是否序列化 + * + * @param f + * @return + */ + public static boolean isSerializable(Field f) { + Class[] cls = f.getType().getInterfaces(); + for (Class c : cls) { + if (Serializable.class == c) { + return true; + } + } + return false; + } + + /** + * 设置域的值 + * + * @param f + * @param obj + * @return + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + public static Object set(Field f, Object obj, Object value) throws IllegalArgumentException, IllegalAccessException { + f.setAccessible(true); + f.set(obj, value); + return f.get(obj); + } + + /** + * 获取域的值 + * + * @param f + * @param obj + * @return + * @throws IllegalAccessException + * @throws IllegalArgumentException + */ + public static Object get(Field f, Object obj) throws IllegalArgumentException, IllegalAccessException { + f.setAccessible(true); + return f.get(obj); + } + + public static boolean isLong(Field field) { + return field.getType() == long.class || field.getType() == Long.class; + } + + public static boolean isInteger(Field field) { + return field.getType() == int.class || field.getType() != Integer.class; + } + + /** + * 获取域的泛型类型,如果不带泛型返回null + * + * @param f + * @return + */ + public static Class getGenericType(Field f) { + Type type = f.getGenericType(); + if (type instanceof ParameterizedType) { + type = ((ParameterizedType) type).getActualTypeArguments()[0]; + if (type instanceof Class) return (Class) type; + } else if (type instanceof Class) return (Class) type; + return null; + } + + /** + * 获取数组的类型 + * + * @param f + * @return + */ + public static Class getComponentType(Field f) { + return f.getType().getComponentType(); + } + + /** + * 获取全部Field,包括父类 + * + * @param claxx + * @return + */ + public static List getAllDeclaredFields(Class claxx) { + // find all field. + LinkedList fieldList = new LinkedList(); + while (claxx != null && claxx != Object.class) { + Field[] fs = claxx.getDeclaredFields(); + for (int i = 0; i < fs.length; i++) { + Field f = fs[i]; + if (!isInvalid(f)) { + fieldList.addLast(f); + } + } + claxx = claxx.getSuperclass(); + } + return fieldList; + } + + /** + * 是静态常量或者内部结构属性 + * + * @param f + * @return + */ + public static boolean isInvalid(Field f) { + return (Modifier.isStatic(f.getModifiers()) && Modifier.isFinal(f.getModifiers())) || f.isSynthetic(); + } +} diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..aec9973 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,90 +1,90 @@ -@if "%DEBUG%" == "" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - -set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto init - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% - -:end -@rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From d9dbdf87ed520601f6867f5c455309df97d71d0a Mon Sep 17 00:00:00 2001 From: luffykou Date: Thu, 4 Aug 2016 09:47:47 +0800 Subject: [PATCH 4/4] change project to android studio type --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5c3d3b1..a9219ea 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,17 @@ Android Common Utils or Helper. Such as Log, Averager, Base64, Check, FlashLight, KeyguardLock, LogReader, Network, SilentInstaller, TimeAverager, TimeCounter, Toastor, WakeLock, ScreenReceiver, SmsReceiver, PhoneReceiver, NotificationService, AndroidUtil, AppUtil, BitmapUtil, ByteUtil, ClassUtil, DialogUtil, FieldUtil, FileUtil, HexUtil, MD5Util, NotificationUtil, NumberUtil, PackageUtil, RandomUtil, ShellUtil, TelephoneUtil, VibrateUtil, IOUtils, FileUtils, AsyncExecutor, etc. - +##使用方法 + +build.gradle文件中添加: + + dependencies { + ... + compile 'com.luffykou:android-common-utils:1.1.3' + } + +##详细介绍 + LiteCommon是一系列通用类、辅助类、工具类的集合,有以下特点: - **1. 通用性强**:只有常用、通用才集入。 - **2. 体积超小**:不到50K!加入增强IO包混淆后70K!