From 229baa6ca83cf2cbbe5501a68b66b65d0c82fe2c Mon Sep 17 00:00:00 2001 From: calvin1978 Date: Sat, 14 Jan 2017 11:25:33 +0800 Subject: [PATCH] =?UTF-8?q?#2=20=E5=8A=A0=E5=BC=BAUtils=20Modules=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=20Random=EF=BC=8CHash=E7=9A=84=E6=80=A7=E8=83=BD?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../examples/bootapi/api/AccountEndPoint.java | 2 +- .../examples/bootapi/api/BookEndpoint.java | 2 +- .../api/support/CustomExceptionHandler.java | 2 +- .../api/support/ErrorPageController.java | 2 +- .../showcase/web/HelloController.java | 2 +- .../modules/web}/CollectionExtractor.java | 2 +- .../modules/{mapper => web}/JaxbMapper.java | 2 +- .../springside/modules/web}/MediaTypes.java | 2 +- .../mapper}/CollectionExtractorTest.java | 3 +- .../modules/mapper/JaxbMapperTest.java | 1 + .../modules/utils/base/PropertiesUtil.java | 92 ++++++++++----- .../springside/modules/utils/io/FileUtil.java | 67 +++++++++-- .../springside/modules/utils/io/IOUtil.java | 1 + .../io/{ => type}/StringBuilderWriter.java | 2 +- .../springside/modules/utils/net/NetUtil.java | 4 +- .../modules/utils/number/MathUtil.java | 12 ++ .../modules/utils/number/NumberUtil.java | 14 +++ .../modules/utils/number/RandomUtil.java | 43 +++---- .../modules/utils/text/HashUtil.java | 108 +++++++++-------- .../modules/utils/time/DateUtil.java | 17 ++- .../utils/base/PropertiesUtilTest.java | 111 +++++++++++++----- .../concurrent/ThreadLocalContextTest.java | 2 + .../modules/utils/io/FileUtilTest.java | 2 +- .../modules/utils/io/IOUtilTest.java | 1 + 24 files changed, 341 insertions(+), 155 deletions(-) rename modules/{utils/src/main/java/org/springside/modules/utils/collection => core/src/main/java/org/springside/modules/web}/CollectionExtractor.java (98%) rename modules/core/src/main/java/org/springside/modules/{mapper => web}/JaxbMapper.java (99%) rename modules/{utils/src/main/java/org/springside/modules/utils/net => core/src/main/java/org/springside/modules/web}/MediaTypes.java (97%) rename modules/{utils/src/test/java/org/springside/modules/utils/collection => core/src/test/java/org/springside/modules/mapper}/CollectionExtractorTest.java (94%) rename modules/utils/src/main/java/org/springside/modules/utils/io/{ => type}/StringBuilderWriter.java (99%) diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java index 8ee76033..7084d4c3 100644 --- a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/AccountEndPoint.java @@ -13,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springside.examples.bootapi.service.AccountService; import org.springside.examples.bootapi.service.exception.ErrorCode; import org.springside.examples.bootapi.service.exception.ServiceException; -import org.springside.modules.utils.net.MediaTypes; +import org.springside.modules.web.MediaTypes; // Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎. @RestController diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java index 0ec9f08b..7416f584 100644 --- a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/BookEndpoint.java @@ -20,8 +20,8 @@ import org.springside.examples.bootapi.service.BookAdminService; import org.springside.examples.bootapi.service.BookBorrowService; import org.springside.examples.bootapi.service.exception.ErrorCode; import org.springside.examples.bootapi.service.exception.ServiceException; -import org.springside.modules.utils.net.MediaTypes; import org.springside.modules.utils.reflect.BeanMapper; +import org.springside.modules.web.MediaTypes; // Spring Restful MVC Controller的标识, 直接输出内容,不调用template引擎. @RestController diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java index 08323e4c..829cd874 100644 --- a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/CustomExceptionHandler.java @@ -16,8 +16,8 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.WebRequest; import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.springside.examples.bootapi.service.exception.ServiceException; -import org.springside.modules.utils.net.MediaTypes; import org.springside.modules.utils.text.JsonMapper; +import org.springside.modules.web.MediaTypes; import com.google.common.collect.Maps; diff --git a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java index c0513d8a..7f50e631 100644 --- a/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java +++ b/examples/boot-api/src/main/java/org/springside/examples/bootapi/api/support/ErrorPageController.java @@ -15,8 +15,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.ServletRequestAttributes; -import org.springside.modules.utils.net.MediaTypes; import org.springside.modules.utils.text.JsonMapper; +import org.springside.modules.web.MediaTypes; /** * 重载替换Spring Boot默认的BasicErrorController, 增加日志并让错误返回方式统一. diff --git a/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java index 4294d857..b6bc04df 100644 --- a/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java +++ b/examples/boot-showcase/src/main/java/org/springside/examples/showcase/web/HelloController.java @@ -4,7 +4,7 @@ import org.javasimon.aop.Monitored; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springside.modules.utils.net.MediaTypes; +import org.springside.modules.web.MediaTypes; @RestController public class HelloController { diff --git a/modules/utils/src/main/java/org/springside/modules/utils/collection/CollectionExtractor.java b/modules/core/src/main/java/org/springside/modules/web/CollectionExtractor.java similarity index 98% rename from modules/utils/src/main/java/org/springside/modules/utils/collection/CollectionExtractor.java rename to modules/core/src/main/java/org/springside/modules/web/CollectionExtractor.java index 27e4ce2c..4eb3d71c 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/collection/CollectionExtractor.java +++ b/modules/core/src/main/java/org/springside/modules/web/CollectionExtractor.java @@ -1,4 +1,4 @@ -package org.springside.modules.utils.collection; +package org.springside.modules.web; import java.util.ArrayList; import java.util.Collection; diff --git a/modules/core/src/main/java/org/springside/modules/mapper/JaxbMapper.java b/modules/core/src/main/java/org/springside/modules/web/JaxbMapper.java similarity index 99% rename from modules/core/src/main/java/org/springside/modules/mapper/JaxbMapper.java rename to modules/core/src/main/java/org/springside/modules/web/JaxbMapper.java index 3d8442fc..e8085135 100644 --- a/modules/core/src/main/java/org/springside/modules/mapper/JaxbMapper.java +++ b/modules/core/src/main/java/org/springside/modules/web/JaxbMapper.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ -package org.springside.modules.mapper; +package org.springside.modules.web; import java.io.StringReader; import java.io.StringWriter; diff --git a/modules/utils/src/main/java/org/springside/modules/utils/net/MediaTypes.java b/modules/core/src/main/java/org/springside/modules/web/MediaTypes.java similarity index 97% rename from modules/utils/src/main/java/org/springside/modules/utils/net/MediaTypes.java rename to modules/core/src/main/java/org/springside/modules/web/MediaTypes.java index 30daa907..6e7041c0 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/net/MediaTypes.java +++ b/modules/core/src/main/java/org/springside/modules/web/MediaTypes.java @@ -3,7 +3,7 @@ * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ -package org.springside.modules.utils.net; +package org.springside.modules.web; /** * 带UTF-8 charset 定义的MediaType. diff --git a/modules/utils/src/test/java/org/springside/modules/utils/collection/CollectionExtractorTest.java b/modules/core/src/test/java/org/springside/modules/mapper/CollectionExtractorTest.java similarity index 94% rename from modules/utils/src/test/java/org/springside/modules/utils/collection/CollectionExtractorTest.java rename to modules/core/src/test/java/org/springside/modules/mapper/CollectionExtractorTest.java index 7cb6cce4..022ba756 100644 --- a/modules/utils/src/test/java/org/springside/modules/utils/collection/CollectionExtractorTest.java +++ b/modules/core/src/test/java/org/springside/modules/mapper/CollectionExtractorTest.java @@ -3,13 +3,14 @@ * * Licensed under the Apache License, Version 2.0 (the "License"); *******************************************************************************/ -package org.springside.modules.utils.collection; +package org.springside.modules.mapper; import static org.assertj.core.api.Assertions.*; import java.util.List; import org.junit.Test; +import org.springside.modules.web.CollectionExtractor; import com.google.common.collect.Lists; diff --git a/modules/core/src/test/java/org/springside/modules/mapper/JaxbMapperTest.java b/modules/core/src/test/java/org/springside/modules/mapper/JaxbMapperTest.java index d287f05c..f8afbbe0 100644 --- a/modules/core/src/test/java/org/springside/modules/mapper/JaxbMapperTest.java +++ b/modules/core/src/test/java/org/springside/modules/mapper/JaxbMapperTest.java @@ -22,6 +22,7 @@ import org.dom4j.DocumentException; import org.dom4j.DocumentHelper; import org.dom4j.Element; import org.junit.Test; +import org.springside.modules.web.JaxbMapper; import com.google.common.collect.Lists; diff --git a/modules/utils/src/main/java/org/springside/modules/utils/base/PropertiesUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/base/PropertiesUtil.java index 700c8de4..bfab8316 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/base/PropertiesUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/base/PropertiesUtil.java @@ -17,7 +17,7 @@ import org.springside.modules.utils.number.NumberUtil; /** * 关于Properties的工具类 * - * 1. Boolean.readBoolean(name) 与其他类型如Integer.readInteger()等的行为不一致,进行扩展. + * 1. 统一的读取系统变量,其中Boolean.readBoolean的风格不统一,Double则不支持,都进行了扩展. * * 2. 简单的合并系统变量(-D),环境变量 和默认值,以系统变量优先,在未引入Commons Config时使用. * @@ -34,50 +34,84 @@ public abstract class PropertiesUtil { /////////// Boolean.readBoolean()扩展 /////////////// - /** - * 读取Boolean类型的系统变量,为空时返回False,与Boolean.getBoolean()一致. - */ - public static boolean getBooleanDefaultFalse(String name) { - return Boolean.getBoolean(name); - } - /** * 读取Boolean类型的系统变量,为空时返回null,代表未设置,而不是Boolean.getBoolean()的false. */ - public static Boolean getBooleanDefaultNull(String name) { + public static Boolean booleanSystemProperty(String name) { String stringResult = System.getProperty(name); if (stringResult != null) { return Boolean.valueOf(stringResult); } - return null; // NOSONAR + return null; } /** * 读取Boolean类型的系统变量,为空时返回默认值, 而不是Boolean.getBoolean()的false. - * - * 因为默认值为Boolean,返回值也是Boolean */ - public static Boolean getBoolean(String name, Boolean defaultVal) { + public static Boolean booleanSystemProperty(String name, Boolean defaultValue) { String stringResult = System.getProperty(name); if (stringResult != null) { return Boolean.valueOf(stringResult); } else { - return defaultVal; + return defaultValue; } } /** - * 读取Boolean类型的系统变量,为空时返回默认值, 而不是Boolean.getBoolean()的false. - * - * 因为默认值为boolean,返回值也是boolean + * 读取String类型的系统变量,为空时返回null. */ - public static boolean getBoolean(String name, boolean defaultVal) { - String stringResult = System.getProperty(name); - if (stringResult != null) { - return Boolean.parseBoolean(stringResult); - } else { - return defaultVal; - } + public static String stringSystemProperty(String name) { + return System.getProperty(name); + } + + /** + * 读取String类型的系统变量,为空时返回默认值 + */ + public static String stringSystemProperty(String name, String defaultValue) { + return System.getProperty(name, defaultValue); + } + + /** + * 读取Integer类型的系统变量,为空时返回null. + */ + public static Integer intSystemProperty(String name) { + return Integer.getInteger(name); + } + + /** + * 读取Integer类型的系统变量,为空时返回默认值 + */ + public static Integer intSystemProperty(String name, Integer defaultValue) { + return Integer.getInteger(name, defaultValue); + } + + /** + * 读取Long类型的系统变量,为空时返回null. + */ + public static Long longSystemProperty(String name) { + return Long.getLong(name); + } + + /** + * 读取Integer类型的系统变量,为空时返回默认值 + */ + public static Long longSystemProperty(String name, Long defaultValue) { + return Long.getLong(name, defaultValue); + } + + /** + * 读取Double类型的系统变量,为空时返回null. + */ + public static Double doubleSystemProperty(String propertyName) { + return NumberUtil.toDoubleObject(System.getProperty(propertyName)); + } + + /** + * 读取Double类型的系统变量,为空时返回默认值. + */ + public static Double doubleSystemProperty(String propertyName, Double defaultValue) { + Double propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName)); + return propertyValue != null ? propertyValue : defaultValue; } /////////// 简单的合并系统变量(-D),环境变量 和默认值,以系统变量优先./////////////// @@ -85,7 +119,7 @@ public abstract class PropertiesUtil { /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ - public static String readString(String propertyName, String envName, String defaultValue) { + public static String stringSystemProperty(String propertyName, String envName, String defaultValue) { checkEnvName(envName); String propertyValue = System.getProperty(propertyName); if (propertyValue != null) { @@ -99,7 +133,7 @@ public abstract class PropertiesUtil { /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ - public static Integer readInt(String propertyName, String envName, Integer defaultValue) { + public static Integer intSystemProperty(String propertyName, String envName, Integer defaultValue) { checkEnvName(envName); Integer propertyValue = NumberUtil.toIntObject(System.getProperty(propertyName)); if (propertyValue != null) { @@ -113,7 +147,7 @@ public abstract class PropertiesUtil { /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ - public static Long readLong(String propertyName, String envName, Long defaultValue) { + public static Long longSystemProperty(String propertyName, String envName, Long defaultValue) { checkEnvName(envName); Long propertyValue = NumberUtil.toLongObject(System.getProperty(propertyName)); if (propertyValue != null) { @@ -127,7 +161,7 @@ public abstract class PropertiesUtil { /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ - public static Double readDouble(String propertyName, String envName, Double defaultValue) { + public static Double doubleSystemProperty(String propertyName, String envName, Double defaultValue) { checkEnvName(envName); Double propertyValue = NumberUtil.toDoubleObject(System.getProperty(propertyName)); if (propertyValue != null) { @@ -141,7 +175,7 @@ public abstract class PropertiesUtil { /** * 合并系统变量(-D),环境变量 和默认值,以系统变量优先 */ - public static Boolean readBoolean(String propertyName, String envName, Boolean defaultValue) { + public static Boolean booleanSystemProperty(String propertyName, String envName, Boolean defaultValue) { checkEnvName(envName); Boolean propertyValue = toBooleanObject(System.getProperty(propertyName)); if (propertyValue != null) { diff --git a/modules/utils/src/main/java/org/springside/modules/utils/io/FileUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/io/FileUtil.java index 23f2428f..f5392317 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/io/FileUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/io/FileUtil.java @@ -99,25 +99,57 @@ public abstract class FileUtil { return Files.newWriter(getFileByPath(fileName), Charsets.UTF_8); } - ///// 文件操作///// + ///// 文件操作 ///// + + /** + * 复制文件或目录 + */ + public static void copy(File from, File to) throws IOException { + if (from.isDirectory()) + copyDir(from, to); + else + copyFile(from, to); + } /** * 文件复制. * * 如果文件不存在或者是目录,不做修改 */ - public static void copy(@Nullable File from, File to) throws IOException { + public static void copyFile(@Nullable File from, File to) throws IOException { if (isFileExists(from)) { Files.copy(from, to); } } + /** + * 复制目录 + */ + public static void copyDir(File from, File to) throws IOException { + if (to.exists()) { + if (!to.isDirectory()) + throw new IllegalArgumentException(to.toString()); + } else { + to.mkdirs(); + } + + File[] files = from.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + String name = files[i].getName(); + if (".".equals(name) || "..".equals(name)) + continue; + copy(files[i], new File(to, name)); + } + } + } + /** * 文件移动/重命名. * * 如果文件不存在或者是目录,不做修改. */ - public static void move(@Nullable File from, File to) throws IOException { + public static void moveFile(@Nullable File from, File to) throws IOException { if (isFileExists(from)) { Files.move(from, to); } @@ -155,7 +187,7 @@ public abstract class FileUtil { if (!isDirExists(dir)) { return; } - //后序遍历,先删掉子目录中的文件/目录 + // 后序遍历,先删掉子目录中的文件/目录 Iterator iterator = Files.fileTreeTraverser().postOrderTraversal(dir).iterator(); while (iterator.hasNext()) { iterator.next().delete(); @@ -182,13 +214,7 @@ public abstract class FileUtil { * 前序递归列出所有文件, 只包含后缀名匹配的文件. (后缀名不包含.) */ public static List listFileWithExtension(final File rootDir, final String extension) { - return Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(new Predicate() { - - @Override - public boolean apply(File input) { - return input.isFile() && extension.equals(FilePathUtil.getFileExtension(input)); - } - }).toList(); + return Files.fileTreeTraverser().preOrderTraversal(rootDir).filter(new FileExtensionFilter(extension)).toList(); } /** @@ -219,6 +245,9 @@ public abstract class FileUtil { return dir.exists() && dir.isDirectory(); } + /** + * 确保目录存在, 如不存在则创建 + */ public static void makeSureDirExists(String dirPath) throws IOException { makeSureDirExists(getFileByPath(dirPath)); } @@ -289,4 +318,20 @@ public abstract class FileUtil { private static File getFileByPath(String filePath) { return StringUtils.isBlank(filePath) ? null : new File(filePath); } + + /** + * 以文件名后缀做filter,配合fileTreeTraverser使用 + */ + public static final class FileExtensionFilter implements Predicate { + private final String extension; + + private FileExtensionFilter(String extension) { + this.extension = extension; + } + + @Override + public boolean apply(File input) { + return input.isFile() && extension.equals(FilePathUtil.getFileExtension(input)); + } + } } diff --git a/modules/utils/src/main/java/org/springside/modules/utils/io/IOUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/io/IOUtil.java index b7fa3aab..5e5e1593 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/io/IOUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/io/IOUtil.java @@ -15,6 +15,7 @@ import java.util.zip.ZipFile; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springside.modules.utils.io.type.StringBuilderWriter; import org.springside.modules.utils.text.Charsets; import com.google.common.annotations.Beta; diff --git a/modules/utils/src/main/java/org/springside/modules/utils/io/StringBuilderWriter.java b/modules/utils/src/main/java/org/springside/modules/utils/io/type/StringBuilderWriter.java similarity index 99% rename from modules/utils/src/main/java/org/springside/modules/utils/io/StringBuilderWriter.java rename to modules/utils/src/main/java/org/springside/modules/utils/io/type/StringBuilderWriter.java index 232f6219..c1897c5b 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/io/StringBuilderWriter.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/io/type/StringBuilderWriter.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.springside.modules.utils.io; +package org.springside.modules.utils.io.type; import java.io.Serializable; import java.io.Writer; diff --git a/modules/utils/src/main/java/org/springside/modules/utils/net/NetUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/net/NetUtil.java index cd71f14e..48b991bf 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/net/NetUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/net/NetUtil.java @@ -149,10 +149,10 @@ public abstract class NetUtil { */ private static InetAddress findLocalAddressViaNetworkInterface() { // 如果hostname +/etc/hosts 得到的是127.0.0.1, 则首选这块网卡 - String preferNamePrefix = PropertiesUtil.readString("localhost.prefer.nic.prefix", + String preferNamePrefix = PropertiesUtil.stringSystemProperty("localhost.prefer.nic.prefix", "LOCALHOST_PREFER_NIC_PREFIX", "bond0."); // 如果hostname +/etc/hosts 得到的是127.0.0.1, 和首选网卡都不符合要求,则按顺序遍历下面的网卡 - String defaultNicList = PropertiesUtil.readString("localhost.default.nic.list", "LOCALHOST_DEFAULT_NIC_LIST", + String defaultNicList = PropertiesUtil.stringSystemProperty("localhost.default.nic.list", "LOCALHOST_DEFAULT_NIC_LIST", "bond0,eth0,em0,br0"); InetAddress resultAddress = null; diff --git a/modules/utils/src/main/java/org/springside/modules/utils/number/MathUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/number/MathUtil.java index 2ca6dcf4..101f21ec 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/number/MathUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/number/MathUtil.java @@ -83,4 +83,16 @@ public abstract class MathUtil { public static long gcd(long a, long b) { return LongMath.gcd(a, b); } + + /** + * 保证结果为正数的取模,from ElasticSearch + */ + public static int mod(int v, int m) { + int r = v % m; + if (r < 0) { + r += m; + } + return r; + } + } diff --git a/modules/utils/src/main/java/org/springside/modules/utils/number/NumberUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/number/NumberUtil.java index 146b4b80..59ef0e21 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/number/NumberUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/number/NumberUtil.java @@ -27,6 +27,13 @@ public abstract class NumberUtil { return Longs.toByteArray(value); } + /** + * from ElasticSearch Numbers + */ + public static byte[] toBytes(double val) { + return toBytes(Double.doubleToRawLongBits(val)); + } + public static int toInt(byte[] bytes) { return Ints.fromByteArray(bytes); } @@ -35,6 +42,13 @@ public abstract class NumberUtil { return Longs.fromByteArray(bytes); } + /** + * from ElasticSearch Numbers + */ + public static double toDouble(byte[] bytes) { + return Double.longBitsToDouble(toLong(bytes)); + } + /////// 判断字符串类型////////// /** * 判断字符串是否合法数字 diff --git a/modules/utils/src/main/java/org/springside/modules/utils/number/RandomUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/number/RandomUtil.java index 994fccf5..1106a9d7 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/number/RandomUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/number/RandomUtil.java @@ -11,23 +11,25 @@ import org.springside.modules.utils.base.Platforms; /** * 随机数工具集. * - * 1. 获取无锁的ThreadLocalRandom + * 1. 获取无锁的兼容JDK6的ThreadLocalRandom * * 2. 获取性能较佳的SecureRandom * - * 3. 保证没有负数陷阱,也能更精确she定范围的nextInt 与 nextLong(copy from Common Lang RandomUtils) + * 3. 保证没有负数陷阱,也能更精确设定范围的nextInt/nextLong/nextDouble + * (copy from Common Lang RandomUtils,但默认使用性能较优的ThreadLocalRandom,并可配置其他的Random) + * + * 4. 随机字符串 * * @author calvin */ public abstract class RandomUtil { - private static final Random RANDOM = new Random(); - + /////////////////// 获取Random实例////////////// /** * 返回无锁的ThreadLocalRandom * - * 如果JDK6,使用移植于JDK7的版本,否则使用JDK自带 + * 如果JDK6,使用移植于jsr166e的版本(相当于JDK7的实现) */ public static Random threadLocalRandom() { if (Platforms.IS_ATLEASET_JAVA7) { @@ -57,7 +59,7 @@ public abstract class RandomUtil { * 返回0到Intger.MAX_VALUE的随机Int, 使用内置全局普通Random. */ public static int nextInt() { - return nextInt(RANDOM); + return nextInt(threadLocalRandom()); } /** @@ -78,7 +80,7 @@ public abstract class RandomUtil { * 返回0到max的随机Int, 使用内置全局普通Random. */ public static int nextInt(int max) { - return nextInt(RANDOM, max); + return nextInt(threadLocalRandom(), max); } /** @@ -94,7 +96,7 @@ public abstract class RandomUtil { * min必须大于0. */ public static int nextInt(int min, int max) { - return nextInt(RANDOM, min, max); + return nextInt(threadLocalRandom(), min, max); } /** @@ -122,7 +124,7 @@ public abstract class RandomUtil { * 返回0-Long.MAX_VALUE间的随机Long, 使用内置全局普通Random. */ public static long nextLong() { - return nextLong(RANDOM); + return nextLong(threadLocalRandom()); } /** @@ -142,7 +144,7 @@ public abstract class RandomUtil { * 返回0-max间的随机Long, 使用内置全局普通Random. */ public static long nextLong(long max) { - return nextLong(RANDOM, 0, max); + return nextLong(threadLocalRandom(), 0, max); } /** @@ -158,7 +160,7 @@ public abstract class RandomUtil { * min必须大于0. */ public static long nextLong(long min, long max) { - return nextLong(RANDOM, min, max); + return nextLong(threadLocalRandom(), min, max); } /** @@ -186,7 +188,7 @@ public abstract class RandomUtil { * 返回0-之间的double */ public static double nextDouble() { - return nextDouble(RANDOM, 0, Double.MAX_VALUE); + return nextDouble(threadLocalRandom(), 0, Double.MAX_VALUE); } /** @@ -200,7 +202,7 @@ public abstract class RandomUtil { * 返回0-max之间的double */ public static double nextDouble(double max) { - return nextDouble(RANDOM, 0, max); + return nextDouble(threadLocalRandom(), 0, max); } /** @@ -214,7 +216,7 @@ public abstract class RandomUtil { * 返回min-max之间的double */ public static double nextDouble(final double min, final double max) { - return nextDouble(RANDOM, min, max); + return nextDouble(threadLocalRandom(), min, max); } /** @@ -236,7 +238,7 @@ public abstract class RandomUtil { * 随机字母或数字,固定长度 */ public static String randomStringFixLength(int length) { - return RandomStringUtils.random(length, 0, 0, true, true, null, RANDOM); + return RandomStringUtils.random(length, 0, 0, true, true, null, threadLocalRandom()); } /** @@ -250,7 +252,7 @@ public abstract class RandomUtil { * 随机字母或数字,随机长度 */ public static String randomStringRandomLength(int minLength, int maxLength) { - return RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, true, null, RANDOM); + return RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, true, null, threadLocalRandom()); } /** @@ -264,7 +266,7 @@ public abstract class RandomUtil { * 随机字母,固定长度 */ public static String randomLetterFixLength(int length) { - return RandomStringUtils.random(length, 0, 0, true, false, null, RANDOM); + return RandomStringUtils.random(length, 0, 0, true, false, null, threadLocalRandom()); } /** @@ -278,7 +280,7 @@ public abstract class RandomUtil { * 随机字母,随机长度 */ public static String randomLetterRandomLength(int minLength, int maxLength) { - return RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, false, null, RANDOM); + return RandomStringUtils.random(nextInt(minLength, maxLength), 0, 0, true, false, null, threadLocalRandom()); } /** @@ -292,7 +294,7 @@ public abstract class RandomUtil { * 随机ASCII字符(含字母,数字及其他符号),固定长度 */ public static String randomAsciiFixLength(int length) { - return RandomStringUtils.random(length, 32, 127, false, false, null, RANDOM); + return RandomStringUtils.random(length, 32, 127, false, false, null, threadLocalRandom()); } /** @@ -306,7 +308,8 @@ public abstract class RandomUtil { * 随机ASCII字符(含字母,数字及其他符号),随机长度 */ public static String randomAsciiRandomLength(int minLength, int maxLength) { - return RandomStringUtils.random(nextInt(minLength, maxLength), 32, 127, false, false, null, RANDOM); + return RandomStringUtils.random(nextInt(minLength, maxLength), 32, 127, false, false, null, + threadLocalRandom()); } /** diff --git a/modules/utils/src/main/java/org/springside/modules/utils/text/HashUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/text/HashUtil.java index 66e126e6..55c95144 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/text/HashUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/text/HashUtil.java @@ -7,8 +7,8 @@ package org.springside.modules.utils.text; import java.io.IOException; import java.io.InputStream; -import java.security.GeneralSecurityException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.Collection; @@ -16,7 +16,6 @@ import java.util.Iterator; import java.util.zip.CRC32; import org.apache.commons.lang3.Validate; -import org.springside.modules.utils.base.ExceptionUtil; import com.google.common.hash.Hashing; @@ -25,11 +24,13 @@ import com.google.common.hash.Hashing; * * 1.集合类的HashCode, 返回int * - * 2.SHA-1, 安全性较高, 返回byte[](可用Encodes进一步被编码为Hex, Base64), + * 2.SHA-1, 安全性较高, 返回byte[](可用Encodes进一步被编码为Hex, Base64) + * + * 性能优化,使用ThreadLocal的MessageDigest(from ElasticSearch) * * 支持带salt并且进行迭代达到更高的安全性. * - * MD5的安全性较低, 默认已不支持. + * MD5的安全性较低, 只在文件Checksum时支持. * * 3.crc32, murmur32这些不追求安全性, 性能较高, 返回int. * @@ -39,8 +40,22 @@ import com.google.common.hash.Hashing; */ public abstract class HashUtil { - private static final String SHA1_ALG = "SHA-1"; - private static final String MD5_ALG = "MD5"; + private static ThreadLocal createThreadLocalMessageDigest(final String digest) { + return new ThreadLocal() { + @Override + protected MessageDigest initialValue() { + try { + return MessageDigest.getInstance(digest); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException( + "unexpected exception creating MessageDigest instance for [" + digest + "]", e); + } + } + }; + } + + private static final ThreadLocal MD5_DIGEST = createThreadLocalMessageDigest("MD5"); + private static final ThreadLocal SHA_1_DIGEST = createThreadLocalMessageDigest("SHA-1"); private static SecureRandom random = new SecureRandom(); @@ -74,28 +89,28 @@ public abstract class HashUtil { * 对输入字符串进行sha1散列. */ public static byte[] sha1(byte[] input) { - return digest(input, SHA1_ALG, null, 1); + return digest(input, get(SHA_1_DIGEST), null, 1); } /** * 对输入字符串进行sha1散列, 编码默认为UTF8. */ public static byte[] sha1(String input) { - return digest(input.getBytes(Charsets.UTF_8), SHA1_ALG, null, 1); + return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), null, 1); } /** * 对输入字符串进行sha1散列,带salt达到更高的安全性. */ public static byte[] sha1(byte[] input, byte[] salt) { - return digest(input, SHA1_ALG, salt, 1); + return digest(input, get(SHA_1_DIGEST), salt, 1); } /** * 对输入字符串进行sha1散列,带salt达到更高的安全性. */ public static byte[] sha1(String input, byte[] salt) { - return digest(input.getBytes(Charsets.UTF_8), SHA1_ALG, salt, 1); + return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, 1); } /** @@ -104,7 +119,7 @@ public abstract class HashUtil { * @see #generateSalt(int) */ public static byte[] sha1(byte[] input, byte[] salt, int iterations) { - return digest(input, SHA1_ALG, salt, iterations); + return digest(input, get(SHA_1_DIGEST), salt, iterations); } /** @@ -113,34 +128,34 @@ public abstract class HashUtil { * @see #generateSalt(int) */ public static byte[] sha1(String input, byte[] salt, int iterations) { - return digest(input.getBytes(Charsets.UTF_8), SHA1_ALG, salt, iterations); + return digest(input.getBytes(Charsets.UTF_8), get(SHA_1_DIGEST), salt, iterations); + } + + private static MessageDigest get(ThreadLocal messageDigest) { + MessageDigest instance = messageDigest.get(); + instance.reset(); + return instance; } /** * 对字符串进行散列, 支持md5与sha1算法. */ - private static byte[] digest(byte[] input, String algorithm, byte[] salt, int iterations) { - try { - MessageDigest digest = MessageDigest.getInstance(algorithm); - - // 带盐 - if (salt != null) { - digest.update(salt); - } - - // 第一次散列 - byte[] result = digest.digest(input); - - // 如果迭代次数>1,进一步迭代散列 - for (int i = 1; i < iterations; i++) { - digest.reset(); - result = digest.digest(result); - } - - return result; - } catch (GeneralSecurityException e) { - throw ExceptionUtil.unchecked(e); + private static byte[] digest(byte[] input, MessageDigest digest, byte[] salt, int iterations) { + // 带盐 + if (salt != null) { + digest.update(salt); } + + // 第一次散列 + byte[] result = digest.digest(input); + + // 如果迭代次数>1,进一步迭代散列 + for (int i = 1; i < iterations; i++) { + digest.reset(); + result = digest.digest(result); + } + + return result; } /** @@ -160,32 +175,27 @@ public abstract class HashUtil { * 对文件进行sha1散列. */ public static byte[] sha1File(InputStream input) throws IOException { - return digestFile(input, SHA1_ALG); + return digestFile(input, get(SHA_1_DIGEST)); } /** * 对文件进行md5散列,被破解后MD5已较少人用. */ public static byte[] md5File(InputStream input) throws IOException { - return digestFile(input, MD5_ALG); + return digestFile(input, get(MD5_DIGEST)); } - private static byte[] digestFile(InputStream input, String algorithm) throws IOException { - try { - MessageDigest messageDigest = MessageDigest.getInstance(algorithm); - int bufferLength = 8 * 1024; - byte[] buffer = new byte[bufferLength]; - int read = input.read(buffer, 0, bufferLength); + private static byte[] digestFile(InputStream input, MessageDigest messageDigest) throws IOException { + int bufferLength = 8 * 1024; + byte[] buffer = new byte[bufferLength]; + int read = input.read(buffer, 0, bufferLength); - while (read > -1) { - messageDigest.update(buffer, 0, read); - read = input.read(buffer, 0, bufferLength); - } - - return messageDigest.digest(); - } catch (GeneralSecurityException e) { - throw ExceptionUtil.unchecked(e); + while (read > -1) { + messageDigest.update(buffer, 0, read); + read = input.read(buffer, 0, bufferLength); } + + return messageDigest.digest(); } ////////////////// 基于JDK的CRC32 /////////////////// diff --git a/modules/utils/src/main/java/org/springside/modules/utils/time/DateUtil.java b/modules/utils/src/main/java/org/springside/modules/utils/time/DateUtil.java index d39471a5..26be633a 100644 --- a/modules/utils/src/main/java/org/springside/modules/utils/time/DateUtil.java +++ b/modules/utils/src/main/java/org/springside/modules/utils/time/DateUtil.java @@ -26,6 +26,7 @@ public abstract class DateUtil { private static final int[] MONTH_LENGTH = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + //////// 日期比较 /////////// /** * 是否同一天. * @@ -39,9 +40,23 @@ public abstract class DateUtil { * 是否同一时刻. */ public static boolean isSameTime(@NotNull final Date date1, @NotNull final Date date2) { - return date1.getTime() == date2.getTime(); + // date.getMillisOf() 比date.getTime()快 + return date1.compareTo(date2) == 0; } + /** + * 判断日期是否在范围内,包含相等的日期 + */ + public static boolean isBetween(@NotNull final Date date, @NotNull final Date start, @NotNull final Date end) { + if (date == null || start == null || end == null || start.after(end)) { + throw new IllegalArgumentException("some date parameters is null or dateBein after dateEnd"); + } + return !date.before(start) && !date.after(end); + } + + + /////////// 日期设置处理 ///////// + /** * 日期往下取整. * diff --git a/modules/utils/src/test/java/org/springside/modules/utils/base/PropertiesUtilTest.java b/modules/utils/src/test/java/org/springside/modules/utils/base/PropertiesUtilTest.java index 2b816f1a..d5f1a9fe 100644 --- a/modules/utils/src/test/java/org/springside/modules/utils/base/PropertiesUtilTest.java +++ b/modules/utils/src/test/java/org/springside/modules/utils/base/PropertiesUtilTest.java @@ -12,53 +12,100 @@ import org.springside.modules.utils.number.RandomUtil; public class PropertiesUtilTest { @Test - public void getBoolean() { + public void systemProperty() { String name = "ss.test" + RandomUtil.nextInt(); - boolean result = PropertiesUtil.getBooleanDefaultFalse(name); - assertThat(result).isFalse(); - - Boolean result0 = PropertiesUtil.getBooleanDefaultNull(name); + Boolean result0 = PropertiesUtil.booleanSystemProperty(name); assertThat(result0).isNull(); - Boolean result1 = PropertiesUtil.getBoolean(name, null); + Boolean result1 = PropertiesUtil.booleanSystemProperty(name, null); assertThat(result1).isNull(); - boolean result2 = PropertiesUtil.getBoolean(name, false); - assertThat(result2).isFalse(); - - Boolean result3 = PropertiesUtil.getBoolean(name, Boolean.TRUE); + Boolean result3 = PropertiesUtil.booleanSystemProperty(name, Boolean.TRUE); assertThat(result3).isTrue(); System.setProperty(name, "true"); - Boolean result4 = PropertiesUtil.getBoolean(name, false); - assertThat(result4).isTrue(); - - Boolean result5 = PropertiesUtil.getBoolean(name, Boolean.FALSE); + + Boolean result5 = PropertiesUtil.booleanSystemProperty(name, Boolean.FALSE); assertThat(result5).isTrue(); + + System.clearProperty(name); + + /// int + Integer result6 = PropertiesUtil.intSystemProperty(name); + assertThat(result6).isNull(); + + result6 = PropertiesUtil.intSystemProperty(name, 1); + assertThat(result6).isEqualTo(1); + + System.setProperty(name, "2"); + result6 = PropertiesUtil.intSystemProperty(name, 1); + assertThat(result6).isEqualTo(2); + + System.clearProperty(name); + + ///// long + Long result7 = PropertiesUtil.longSystemProperty(name); + assertThat(result7).isNull(); + + result7 = PropertiesUtil.longSystemProperty(name, 1L); + assertThat(result7).isEqualTo(1L); + + System.setProperty(name, "2"); + result7 = PropertiesUtil.longSystemProperty(name, 1L); + assertThat(result7).isEqualTo(2L); + + System.clearProperty(name); + + ///// doulbe + Double result8 = PropertiesUtil.doubleSystemProperty(name); + assertThat(result8).isNull(); + + result8 = PropertiesUtil.doubleSystemProperty(name, 1.1); + assertThat(result8).isEqualTo(1.1); + + System.setProperty(name, "2.1"); + result8 = PropertiesUtil.doubleSystemProperty(name, 1.1); + assertThat(result8).isEqualTo(2.1); + + System.clearProperty(name); + + ///// String + String result9 = PropertiesUtil.stringSystemProperty(name); + assertThat(result9).isNull(); + + result9 = PropertiesUtil.stringSystemProperty(name, "1.1"); + assertThat(result9).isEqualTo("1.1"); + + System.setProperty(name, "2.1"); + result9 = PropertiesUtil.stringSystemProperty(name, "1.1"); + assertThat(result9).isEqualTo("2.1"); + + System.clearProperty(name); + } @Test - public void readString() { + public void stringSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 - String result = PropertiesUtil.readString(name, envName, "123"); + String result = PropertiesUtil.stringSystemProperty(name, envName, "123"); assertThat(result).isEqualTo("123"); // env值 - String result2 = PropertiesUtil.readString(name, "HOME", "123"); + String result2 = PropertiesUtil.stringSystemProperty(name, "HOME", "123"); assertThat(result2).isNotEqualTo("123"); // system properties值 System.setProperty(name, "456"); - String result3 = PropertiesUtil.readString(name, envName, "123"); + String result3 = PropertiesUtil.stringSystemProperty(name, envName, "123"); assertThat(result3).isEqualTo("456"); try { // 非法字符 - String result4 = PropertiesUtil.readString(name, name, "123"); + String result4 = PropertiesUtil.stringSystemProperty(name, name, "123"); Assert.fail("should fail before"); } catch (Exception e) { assertThat(e).isInstanceOf(IllegalArgumentException.class); @@ -68,81 +115,81 @@ public class PropertiesUtilTest { } @Test - public void readInt() { + public void intSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 - int result = PropertiesUtil.readInt(name, envName, 123); + int result = PropertiesUtil.intSystemProperty(name, envName, 123); assertThat(result).isEqualTo(123); // env值没有数字类型的,忽略 // system properties值 System.setProperty(name, "456"); - int result3 = PropertiesUtil.readInt(name, envName, 123); + int result3 = PropertiesUtil.intSystemProperty(name, envName, 123); assertThat(result3).isEqualTo(456); System.clearProperty(name); } @Test - public void readLong() { + public void longSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 - long result = PropertiesUtil.readLong(name, envName, 123L); + long result = PropertiesUtil.longSystemProperty(name, envName, 123L); assertThat(result).isEqualTo(123L); // env值没有数字类型的,忽略 // system properties值 System.setProperty(name, "456"); - long result3 = PropertiesUtil.readLong(name, envName, 123L); + long result3 = PropertiesUtil.longSystemProperty(name, envName, 123L); assertThat(result3).isEqualTo(456L); System.clearProperty(name); } @Test - public void readDouble() { + public void doubleSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 - double result = PropertiesUtil.readDouble(name, envName, 123d); + double result = PropertiesUtil.doubleSystemProperty(name, envName, 123d); assertThat(result).isEqualTo(123d); // env值没有数字类型的,忽略 // system properties值 System.setProperty(name, "456"); - double result3 = PropertiesUtil.readDouble(name, envName, 123d); + double result3 = PropertiesUtil.doubleSystemProperty(name, envName, 123d); assertThat(result3).isEqualTo(456d); System.clearProperty(name); } @Test - public void readBoolean() { + public void booleanSystemProperty() { String name = "ss.test" + RandomUtil.nextInt(); String envName = "ss_test" + RandomUtil.nextInt(); // default 值 - boolean result = PropertiesUtil.readBoolean(name, envName, true); + boolean result = PropertiesUtil.booleanSystemProperty(name, envName, true); assertThat(result).isTrue(); // env值没有boolean类型的,忽略 // system properties值 System.setProperty(name, "true"); - boolean result3 = PropertiesUtil.readBoolean(name, envName, false); + boolean result3 = PropertiesUtil.booleanSystemProperty(name, envName, false); assertThat(result3).isTrue(); System.clearProperty(name); } - + @Test public void loadProperties() { Properties p1 = PropertiesUtil.loadFromFile("classpath://application.properties"); diff --git a/modules/utils/src/test/java/org/springside/modules/utils/concurrent/ThreadLocalContextTest.java b/modules/utils/src/test/java/org/springside/modules/utils/concurrent/ThreadLocalContextTest.java index e05f6528..a43d518c 100644 --- a/modules/utils/src/test/java/org/springside/modules/utils/concurrent/ThreadLocalContextTest.java +++ b/modules/utils/src/test/java/org/springside/modules/utils/concurrent/ThreadLocalContextTest.java @@ -26,6 +26,8 @@ public class ThreadLocalContextTest { ThreadLocalContext.put("myname", Thread.currentThread().getName()); ThreadUtil.sleep(RandomUtil.nextLong(100, 300)); System.out.println(ThreadLocalContext.get("myname")); + ThreadLocalContext.reset(); + System.out.println("shoud null for " +Thread.currentThread().getName() + ":"+ThreadLocalContext.get("myname")); countdown.countDown(); } }; diff --git a/modules/utils/src/test/java/org/springside/modules/utils/io/FileUtilTest.java b/modules/utils/src/test/java/org/springside/modules/utils/io/FileUtilTest.java index 0b3d3f12..ab940d96 100644 --- a/modules/utils/src/test/java/org/springside/modules/utils/io/FileUtilTest.java +++ b/modules/utils/src/test/java/org/springside/modules/utils/io/FileUtilTest.java @@ -43,7 +43,7 @@ public class FileUtilTest { FileUtil.write(content, file); File newFile = new File(FilePathUtil.contact(Platforms.TMP_DIR, "testFile" + RandomUtil.nextInt())); - FileUtil.copy(file, newFile); + FileUtil.copyFile(file, newFile); assertThat(FileUtil.isFileExists(newFile)).isTrue(); String result = FileUtil.toString(newFile); diff --git a/modules/utils/src/test/java/org/springside/modules/utils/io/IOUtilTest.java b/modules/utils/src/test/java/org/springside/modules/utils/io/IOUtilTest.java index 3e81e151..0571e493 100644 --- a/modules/utils/src/test/java/org/springside/modules/utils/io/IOUtilTest.java +++ b/modules/utils/src/test/java/org/springside/modules/utils/io/IOUtilTest.java @@ -7,6 +7,7 @@ import java.io.Closeable; import java.io.IOException; import org.junit.Test; +import org.springside.modules.utils.io.type.StringBuilderWriter; import org.springside.modules.utils.text.Charsets; public class IOUtilTest {