diff --git a/common-lib/pom.xml b/common-lib/pom.xml new file mode 100644 index 0000000..dd4b089 --- /dev/null +++ b/common-lib/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + cn.axzo.foundation + axzo-lib-box + 2.0.0-SNAPSHOT + + + common-lib + jar + + + 8 + 8 + UTF-8 + + + \ No newline at end of file diff --git a/common-lib/src/main/java/cn/axzo/foundation/enums/AppEnvEnum.java b/common-lib/src/main/java/cn/axzo/foundation/enums/AppEnvEnum.java new file mode 100644 index 0000000..3794980 --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/enums/AppEnvEnum.java @@ -0,0 +1,10 @@ +package cn.axzo.foundation.enums; + +public enum AppEnvEnum { + unittest, + local, + dev, + test, + pre, + prd +} diff --git a/common-lib/src/main/java/cn/axzo/foundation/enums/OrderEnum.java b/common-lib/src/main/java/cn/axzo/foundation/enums/OrderEnum.java new file mode 100644 index 0000000..4df8f17 --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/enums/OrderEnum.java @@ -0,0 +1,7 @@ +package cn.axzo.foundation.enums; + + +public enum OrderEnum { + DESC, + ASC; +} diff --git a/common-lib/src/main/java/cn/axzo/foundation/exception/Axssert.java b/common-lib/src/main/java/cn/axzo/foundation/exception/Axssert.java new file mode 100644 index 0000000..b597c3e --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/exception/Axssert.java @@ -0,0 +1,239 @@ +package cn.axzo.foundation.exception; + +import com.google.common.base.Strings; + +import java.util.Collection; +import java.util.Objects; +import java.util.function.Supplier; + +import static cn.axzo.foundation.result.ResultCode.INVALID_PARAMS; + +public abstract class Axssert { + + /** + * 抛业务异常 + * + * @param errorCode + */ + public static void throwError(IResultCode errorCode) { + throw new BusinessException(errorCode); + } + + /** + * 覆盖errorCode消息并抛业务异常 + * + * @param errorCode + * @param overrideMessage + */ + public static void throwError(IResultCode errorCode, String overrideMessage) { + throw new BusinessException(errorCode.getErrorCode(), overrideMessage); + } + + /** + * 断言表达式为true, 否则抛业务异常 + * + * @param expression + * @param errorCode + */ + public static void isTrue(boolean expression, IResultCode errorCode) { + if (!expression) { + throw new BusinessException(errorCode); + } + } + + /** + * 断言表达式为true, 否则抛业务异常,并覆盖业务异常消息 + * + * @param expression + * @param errorCode + * @param overrideMessage + */ + public static void isTrue(boolean expression, IResultCode errorCode, String overrideMessage) { + if (!expression) { + throw new BusinessException(errorCode.getErrorCode(), overrideMessage); + } + } + + /** + * 断言表达式为false, 否则抛业务异常 + * + * @param expression + * @param errorCode + */ + public static void isFalse(boolean expression, IResultCode errorCode) { + isTrue(!expression, errorCode); + } + + /** + * 断言表达式为false, 否则抛业务异常,并覆盖业务异常消息 + * + * @param expression + * @param errorCode + * @param overrideMessage + */ + public static void isFalse(boolean expression, IResultCode errorCode, String overrideMessage) { + isTrue(!expression, errorCode, overrideMessage); + } + + /** + * 断言对象为null, 否则抛业务异常。 + * + * @param object + * @param errorCode + */ + public static void isNull(Object object, IResultCode errorCode) { + isTrue(object == null, errorCode); + } + + /** + * 断言对象为null, 否则抛业务异常,并覆盖业务异常消息 + * + * @param object + * @param errorCode + * @param overrideMessage + */ + public static void isNull(Object object, IResultCode errorCode, String overrideMessage) { + isTrue(object == null, errorCode, overrideMessage); + } + + /** + * 断言对象非空, 否则抛业务异常,并覆盖业务异常消息 + * + * @param object + * @param errorCode + */ + public static void notNull(Object object, IResultCode errorCode) { + isTrue(object != null, errorCode); + } + + /** + * 断言对象非空, 否则抛业务异常,并覆盖业务异常消息 + * + * @param object + * @param errorCode + * @param overrideMessage + */ + public static void notNull(Object object, IResultCode errorCode, String overrideMessage) { + isTrue(object != null, errorCode, overrideMessage); + } + + public static void check(boolean expect, String code, String msg) { + if (!expect) { + throw new BusinessException(code, msg); + } + } + + public static void check(boolean expect, IResultCode resultCode) { + if (!expect) { + throw resultCode.toException(); + } + } + + public static void check(boolean expect, IResultCode resultCode, String msg, Object... objects) { + if (!expect) { + throw resultCode.toException(msg, objects); + } + } + + public static void check(boolean expect, Supplier supplier) throws T { + if (!expect) { + throw supplier.get(); + } + } + + public static void checkEquals(Object source, Object target, String msg) { + if (!Objects.equals(source, target)) { + throw INVALID_PARAMS.toException(msg); + } + } + + public static void checkEquals(Object source, Object target, IResultCode resultCode) { + if (!Objects.equals(source, target)) { + throw resultCode.toException(); + } + } + + public static void checkEquals(Object source, Object target, String msg, Object... objects) { + if (!Objects.equals(source, target)) { + throw INVALID_PARAMS.toException(msg, objects); + } + } + + public static void checkEquals(Object source, Object target, IResultCode resultCode, String msg, Object... objects) { + if (!Objects.equals(source, target)) { + throw resultCode.toException(msg, objects); + } + } + + public static void checkNonNull(Object target, String msg) { + if (Objects.isNull(target)) { + throw INVALID_PARAMS.toException(msg); + } + } + + public static void checkNonNull(Object target, IResultCode resultCode) { + if (Objects.isNull(target)) { + throw resultCode.toException(); + } + } + + public static void checkNonNull(Object target, String msg, Object... objects) { + if (Objects.isNull(target)) { + throw INVALID_PARAMS.toException(msg, objects); + } + } + + public static void checkNonNull(Object target, IResultCode resultCode, String msg, Object... objects) { + if (Objects.isNull(target)) { + throw resultCode.toException(msg, objects); + } + } + + public static void checkNotEmpty(Collection coll, String msg) { + if (coll == null || coll.isEmpty()) { + throw INVALID_PARAMS.toException(msg); + } + } + + public static void checkNotEmpty(Collection coll, IResultCode resultCode) { + if (coll == null || coll.isEmpty()) { + throw resultCode.toException(); + } + } + + public static void checkNotEmpty(Collection coll, String msg, Object... objects) { + if (coll == null || coll.isEmpty()) { + throw INVALID_PARAMS.toException(msg, objects); + } + } + + public static void checkNotEmpty(Collection coll, IResultCode resultCode, String msg, Object... objects) { + if (coll == null || coll.isEmpty()) { + throw resultCode.toException(msg, objects); + } + } + + public static void checkStringNotEmpty(String str, String msg) { + if (Strings.isNullOrEmpty(str)) { + throw INVALID_PARAMS.toException(msg); + } + } + + public static void checkStringNotEmpty(String str, IResultCode resultCode) { + if (Strings.isNullOrEmpty(str)) { + throw resultCode.toException(); + } + } + + public static void checkStringNotEmpty(String str, String msg, Object... objects) { + if (Strings.isNullOrEmpty(str)) { + throw INVALID_PARAMS.toException(msg, objects); + } + } + + public static void checkStringNotEmpty(String str, IResultCode resultCode, String msg, Object... objects) { + if (Strings.isNullOrEmpty(str)) { + throw resultCode.toException(msg, objects); + } + } +} diff --git a/common-lib/src/main/java/cn/axzo/foundation/exception/BusinessException.java b/common-lib/src/main/java/cn/axzo/foundation/exception/BusinessException.java new file mode 100644 index 0000000..fbe224f --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/exception/BusinessException.java @@ -0,0 +1,52 @@ +package cn.axzo.foundation.exception; + + +import lombok.Getter; + +/** + * 统一业务异常父类 + * + * @author chenliang + * @date 2019-04-29 15:20 + * @since V1.0 + */ +@Getter +public class BusinessException extends RuntimeException { + + private static final long serialVersionUID = -4949212560571865637L; + + public BusinessException(IResultCode resultCode) { + super(String.format("BusinessException{errorCode:%s, errorMsg:%s}", resultCode.getErrorCode(), resultCode.getErrorMessage())); + this.errorCode = resultCode.getErrorCode(); + this.errorMsg = resultCode.getErrorMessage(); + } + + public BusinessException(IResultCode resultCode, Throwable cause) { + super(String.format("BusinessException{errorCode:%s, errorMsg:%s}", resultCode.getErrorCode(), resultCode.getErrorMessage()), cause); + this.errorCode = resultCode.getErrorCode(); + this.errorMsg = resultCode.getErrorMessage(); + } + + public BusinessException(String errorCode, String errorMsg) { + super(String.format("BusinessException{errorCode:%s, errorMsg:%s}", errorCode, errorMsg)); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + public BusinessException(String errorCode, String errorMsg, Throwable cause) { + super(String.format("BusinessException{errorCode:%s, errorMsg:%s}", errorCode, errorMsg), cause); + this.errorCode = errorCode; + this.errorMsg = errorMsg; + } + + /** + * 异常码 + */ + final private String errorCode; + /** + * 异常信息 + */ + final private String errorMsg; + + +} diff --git a/common-lib/src/main/java/cn/axzo/foundation/exception/IResultCode.java b/common-lib/src/main/java/cn/axzo/foundation/exception/IResultCode.java new file mode 100644 index 0000000..370cf3f --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/exception/IResultCode.java @@ -0,0 +1,70 @@ +package cn.axzo.foundation.exception; + +import cn.axzo.foundation.util.VarParamFormatter; + +public interface IResultCode { + + Integer DEFAULT_HTTP_ERROR_CODE = 400; + + /** + * 得到错误码. + * + * @return 错误码. + */ + default Integer getHttpCode() { + return DEFAULT_HTTP_ERROR_CODE; + } + + /** + * 返回错误码 + * + * @return + */ + String getErrorCode(); + + /** + * 返回错误信息 + * + * @return + */ + String getErrorMessage(); + + /** + * 用于快速返回一个BizException, 默认的msg + * + * @return + */ + default BusinessException toException() { + return new BusinessException(this); + } + + /** + * 用于快速返回一个BizException, 自定义的msg + * + * @return + */ + default BusinessException toException(String customMsg) { + return new BusinessException(getErrorCode(), customMsg); + } + + /** + * 用于快速返回一个BizException, 自定义的msg. 支持参数填充 + * + * @return + */ + default BusinessException toException(String customMsg, Object... objects) { + if (objects == null || objects.length == 0) { + return toException(customMsg); + } + + String msg = VarParamFormatter.format(customMsg, objects); + //如果最后一个参数是Throwable. 则将SimpleName附加到msg中 + if (objects[objects.length - 1] instanceof Throwable) { + Throwable throwable = (Throwable) objects[objects.length - 1]; + msg = String.format("%s (%s)", msg, throwable.getClass().getSimpleName()); + } + + return toException(msg); + } + +} diff --git a/common-lib/src/main/java/cn/axzo/foundation/result/ResultCode.java b/common-lib/src/main/java/cn/axzo/foundation/result/ResultCode.java new file mode 100644 index 0000000..8906522 --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/result/ResultCode.java @@ -0,0 +1,29 @@ +package cn.axzo.foundation.result; + +import cn.axzo.foundation.exception.IResultCode; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(access = AccessLevel.PRIVATE) +public enum ResultCode implements IResultCode { + RUNTIME_EXCEPTION("001", "系统异常,请重试", 500), + INVALID_PARAMS("002", "请求参数格式错误", 400); + + final private String code; + + final private String message; + + final private Integer httpCode; + + @Override + public String getErrorCode() { + return code; + } + + @Override + public String getErrorMessage() { + return message; + } +} diff --git a/common-lib/src/main/java/cn/axzo/foundation/util/VarParamFormatter.java b/common-lib/src/main/java/cn/axzo/foundation/util/VarParamFormatter.java new file mode 100644 index 0000000..d428801 --- /dev/null +++ b/common-lib/src/main/java/cn/axzo/foundation/util/VarParamFormatter.java @@ -0,0 +1,637 @@ +package cn.axzo.foundation.util; + + +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * 根据参数格式化message + * {@link org.apache.logging.log4j.message} + */ +public class VarParamFormatter { + /** + * Prefix for recursion. + */ + static final String RECURSION_PREFIX = "[..."; + /** + * Suffix for recursion. + */ + static final String RECURSION_SUFFIX = "...]"; + + /** + * Prefix for errors. + */ + static final String ERROR_PREFIX = "[!!!"; + /** + * Separator for errors. + */ + static final String ERROR_SEPARATOR = "=>"; + /** + * Separator for error messages. + */ + static final String ERROR_MSG_SEPARATOR = ":"; + /** + * Suffix for errors. + */ + static final String ERROR_SUFFIX = "!!!]"; + + private static final char DELIM_START = '{'; + private static final char DELIM_STOP = '}'; + private static final char ESCAPE_CHAR = '\\'; + + private static ThreadLocal threadLocalSimpleDateFormat = new ThreadLocal<>(); + + private VarParamFormatter() { + } + + /** + * Counts the number of unescaped placeholders in the given messagePattern. + * + * @param messagePattern the message pattern to be analyzed. + * @return the number of unescaped placeholders. + */ + static int countArgumentPlaceholders(final String messagePattern) { + if (messagePattern == null) { + return 0; + } + final int length = messagePattern.length(); + int result = 0; + boolean isEscaped = false; + for (int i = 0; i < length - 1; i++) { + final char curChar = messagePattern.charAt(i); + if (curChar == ESCAPE_CHAR) { + isEscaped = !isEscaped; + } else if (curChar == DELIM_START) { + if (!isEscaped && messagePattern.charAt(i + 1) == DELIM_STOP) { + result++; + i++; + } + isEscaped = false; + } else { + isEscaped = false; + } + } + return result; + } + + /** + * Counts the number of unescaped placeholders in the given messagePattern. + * + * @param messagePattern the message pattern to be analyzed. + * @return the number of unescaped placeholders. + */ + static int countArgumentPlaceholders2(final String messagePattern, final int[] indices) { + if (messagePattern == null) { + return 0; + } + final int length = messagePattern.length(); + int result = 0; + boolean isEscaped = false; + for (int i = 0; i < length - 1; i++) { + final char curChar = messagePattern.charAt(i); + if (curChar == ESCAPE_CHAR) { + isEscaped = !isEscaped; + indices[0] = -1; // escaping means fast path is not available... + result++; + } else if (curChar == DELIM_START) { + if (!isEscaped && messagePattern.charAt(i + 1) == DELIM_STOP) { + indices[result] = i; + result++; + i++; + } + isEscaped = false; + } else { + isEscaped = false; + } + } + return result; + } + + /** + * Counts the number of unescaped placeholders in the given messagePattern. + * + * @param messagePattern the message pattern to be analyzed. + * @return the number of unescaped placeholders. + */ + static int countArgumentPlaceholders3(final char[] messagePattern, final int length, final int[] indices) { + int result = 0; + boolean isEscaped = false; + for (int i = 0; i < length - 1; i++) { + final char curChar = messagePattern[i]; + if (curChar == ESCAPE_CHAR) { + isEscaped = !isEscaped; + } else if (curChar == DELIM_START) { + if (!isEscaped && messagePattern[i + 1] == DELIM_STOP) { + indices[result] = i; + result++; + i++; + } + isEscaped = false; + } else { + isEscaped = false; + } + } + return result; + } + + /** + * Replace placeholders in the given messagePattern with arguments. + * + * @param messagePattern the message pattern containing placeholders. + * @param arguments the arguments to be used to replace placeholders. + * @return the formatted message. + */ + public static String format(final String messagePattern, final Object[] arguments) { + final StringBuilder result = new StringBuilder(); + final int argCount = arguments == null ? 0 : arguments.length; + formatMessage(result, messagePattern, arguments, argCount); + return result.toString(); + } + + /** + * Replace placeholders in the given messagePattern with arguments. + * + * @param buffer the buffer to write the formatted message into + * @param messagePattern the message pattern containing placeholders. + * @param arguments the arguments to be used to replace placeholders. + */ + static void formatMessage2(final StringBuilder buffer, final String messagePattern, + final Object[] arguments, final int argCount, final int[] indices) { + if (messagePattern == null || arguments == null || argCount == 0) { + buffer.append(messagePattern); + return; + } + int previous = 0; + for (int i = 0; i < argCount; i++) { + buffer.append(messagePattern, previous, indices[i]); + previous = indices[i] + 2; + recursiveDeepToString(arguments[i], buffer, null); + } + buffer.append(messagePattern, previous, messagePattern.length()); + } + + /** + * Replace placeholders in the given messagePattern with arguments. + * + * @param buffer the buffer to write the formatted message into + * @param messagePattern the message pattern containing placeholders. + * @param arguments the arguments to be used to replace placeholders. + */ + static void formatMessage3(final StringBuilder buffer, final char[] messagePattern, final int patternLength, + final Object[] arguments, final int argCount, final int[] indices) { + if (messagePattern == null) { + return; + } + if (arguments == null || argCount == 0) { + buffer.append(messagePattern); + return; + } + int previous = 0; + for (int i = 0; i < argCount; i++) { + buffer.append(messagePattern, previous, indices[i]); + previous = indices[i] + 2; + recursiveDeepToString(arguments[i], buffer, null); + } + buffer.append(messagePattern, previous, patternLength); + } + + /** + * Replace placeholders in the given messagePattern with arguments. + * + * @param buffer the buffer to write the formatted message into + * @param messagePattern the message pattern containing placeholders. + * @param arguments the arguments to be used to replace placeholders. + */ + static void formatMessage(final StringBuilder buffer, final String messagePattern, + final Object[] arguments, final int argCount) { + if (messagePattern == null || arguments == null || argCount == 0) { + buffer.append(messagePattern); + return; + } + int escapeCounter = 0; + int currentArgument = 0; + int i = 0; + final int len = messagePattern.length(); + for (; i < len - 1; i++) { // last char is excluded from the loop + final char curChar = messagePattern.charAt(i); + if (curChar == ESCAPE_CHAR) { + escapeCounter++; + } else { + if (isDelimPair(curChar, messagePattern, i)) { // looks ahead one char + i++; + + // write escaped escape chars + writeEscapedEscapeChars(escapeCounter, buffer); + + if (isOdd(escapeCounter)) { + // i.e. escaped: write escaped escape chars + writeDelimPair(buffer); + } else { + // unescaped + writeArgOrDelimPair(arguments, argCount, currentArgument, buffer); + currentArgument++; + } + } else { + handleLiteralChar(buffer, escapeCounter, curChar); + } + escapeCounter = 0; + } + } + handleRemainingCharIfAny(messagePattern, len, buffer, escapeCounter, i); + } + + /** + * Returns {@code true} if the specified char and the char at {@code curCharIndex + 1} in the specified message + * pattern together form a "{}" delimiter pair, returns {@code false} otherwise. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 22 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static boolean isDelimPair(final char curChar, final String messagePattern, final int curCharIndex) { + return curChar == DELIM_START && messagePattern.charAt(curCharIndex + 1) == DELIM_STOP; + } + + /** + * Detects whether the message pattern has been fully processed or if an unprocessed character remains and processes + * it if necessary, returning the resulting position in the result char array. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void handleRemainingCharIfAny(final String messagePattern, final int len, + final StringBuilder buffer, final int escapeCounter, final int i) { + if (i == len - 1) { + final char curChar = messagePattern.charAt(i); + handleLastChar(buffer, escapeCounter, curChar); + } + } + + /** + * Processes the last unprocessed character and returns the resulting position in the result char array. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 28 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void handleLastChar(final StringBuilder buffer, final int escapeCounter, final char curChar) { + if (curChar == ESCAPE_CHAR) { + writeUnescapedEscapeChars(escapeCounter + 1, buffer); + } else { + handleLiteralChar(buffer, escapeCounter, curChar); + } + } + + /** + * Processes a literal char (neither an '\' escape char nor a "{}" delimiter pair) and returns the resulting + * position. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 16 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void handleLiteralChar(final StringBuilder buffer, final int escapeCounter, final char curChar) { + // any other char beside ESCAPE or DELIM_START/STOP-combo + // write unescaped escape chars + writeUnescapedEscapeChars(escapeCounter, buffer); + buffer.append(curChar); + } + + /** + * Writes "{}" to the specified result array at the specified position and returns the resulting position. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 18 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void writeDelimPair(final StringBuilder buffer) { + buffer.append(DELIM_START); + buffer.append(DELIM_STOP); + } + + /** + * Returns {@code true} if the specified parameter is odd. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static boolean isOdd(final int number) { + return (number & 1) == 1; + } + + /** + * Writes a '\' char to the specified result array (starting at the specified position) for each pair of + * '\' escape chars encountered in the message format and returns the resulting position. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 11 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void writeEscapedEscapeChars(final int escapeCounter, final StringBuilder buffer) { + final int escapedEscapes = escapeCounter >> 1; // divide by two + writeUnescapedEscapeChars(escapedEscapes, buffer); + } + + /** + * Writes the specified number of '\' chars to the specified result array (starting at the specified position) and + * returns the resulting position. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 20 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void writeUnescapedEscapeChars(int escapeCounter, final StringBuilder buffer) { + while (escapeCounter > 0) { + buffer.append(ESCAPE_CHAR); + escapeCounter--; + } + } + + /** + * Appends the argument at the specified argument index (or, if no such argument exists, the "{}" delimiter pair) to + * the specified result char array at the specified position and returns the resulting position. + */ + // Profiling showed this method is important to log4j performance. Modify with care! + // 25 bytes (allows immediate JVM inlining: < 35 bytes) LOG4J2-1096 + private static void writeArgOrDelimPair(final Object[] arguments, final int argCount, final int currentArgument, + final StringBuilder buffer) { + if (currentArgument < argCount) { + recursiveDeepToString(arguments[currentArgument], buffer, null); + } else { + writeDelimPair(buffer); + } + } + + /** + * This method performs a deep toString of the given Object. + * Primitive arrays are converted using their respective Arrays.toString methods while + * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could + * contain themselves. + *

+ * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a + * behavior. They only check if the container is directly contained in itself, but not if a contained container + * contains the original one. Because of that, Arrays.toString(Object[]) isn't safe either. + * Confusing? Just read the last paragraph again and check the respective toString() implementation. + *

+ *

+ * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o) + * would produce a relatively hard-to-debug StackOverflowError. + *

+ * + * @param o The object. + * @return The String representation. + */ + static String deepToString(final Object o) { + if (o == null) { + return null; + } + if (o instanceof String) { + return (String) o; + } + final StringBuilder str = new StringBuilder(); + final Set dejaVu = new HashSet<>(); // that's actually a neat name ;) + recursiveDeepToString(o, str, dejaVu); + return str.toString(); + } + + /** + * This method performs a deep toString of the given Object. + * Primitive arrays are converted using their respective Arrays.toString methods while + * special handling is implemented for "container types", i.e. Object[], Map and Collection because those could + * contain themselves. + *

+ * dejaVu is used in case of those container types to prevent an endless recursion. + *

+ *

+ * It should be noted that neither AbstractMap.toString() nor AbstractCollection.toString() implement such a + * behavior. + * They only check if the container is directly contained in itself, but not if a contained container contains the + * original one. Because of that, Arrays.toString(Object[]) isn't safe either. + * Confusing? Just read the last paragraph again and check the respective toString() implementation. + *

+ *

+ * This means, in effect, that logging would produce a usable output even if an ordinary System.out.println(o) + * would produce a relatively hard-to-debug StackOverflowError. + *

+ * + * @param o the Object to convert into a String + * @param str the StringBuilder that o will be appended to + * @param dejaVu a list of container identities that were already used. + */ + private static void recursiveDeepToString(final Object o, final StringBuilder str, final Set dejaVu) { + if (appendSpecialTypes(o, str)) { + return; + } + if (isMaybeRecursive(o)) { + appendPotentiallyRecursiveValue(o, str, dejaVu); + } else { + tryObjectToString(o, str); + } + } + + private static boolean appendSpecialTypes(final Object o, final StringBuilder str) { + if (o == null || o instanceof String) { + str.append((String) o); + return true; + } else if (o instanceof CharSequence) { + str.append((CharSequence) o); + return true; + } else if (o instanceof Integer) { + str.append(((Integer) o).intValue()); + return true; + } else if (o instanceof Long) { + str.append(((Long) o).longValue()); + return true; + } else if (o instanceof Double) { + str.append(((Double) o).doubleValue()); + return true; + } else if (o instanceof Boolean) { + str.append(((Boolean) o).booleanValue()); + return true; + } else if (o instanceof Character) { + str.append(((Character) o).charValue()); + return true; + } else if (o instanceof Short) { + str.append(((Short) o).shortValue()); + return true; + } else if (o instanceof Float) { + str.append(((Float) o).floatValue()); + return true; + } + return appendDate(o, str); + } + + private static boolean appendDate(final Object o, final StringBuilder str) { + if (!(o instanceof Date)) { + return false; + } + final Date date = (Date) o; + final SimpleDateFormat format = getSimpleDateFormat(); + str.append(format.format(date)); + return true; + } + + private static SimpleDateFormat getSimpleDateFormat() { + SimpleDateFormat result = threadLocalSimpleDateFormat.get(); + if (result == null) { + result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + threadLocalSimpleDateFormat.set(result); + } + return result; + } + + /** + * Returns {@code true} if the specified object is an array, a Map or a Collection. + */ + private static boolean isMaybeRecursive(final Object o) { + return o.getClass().isArray() || o instanceof Map || o instanceof Collection; + } + + private static void appendPotentiallyRecursiveValue(final Object o, final StringBuilder str, + final Set dejaVu) { + final Class oClass = o.getClass(); + if (oClass.isArray()) { + appendArray(o, str, dejaVu, oClass); + } else if (o instanceof Map) { + appendMap(o, str, dejaVu); + } else if (o instanceof Collection) { + appendCollection(o, str, dejaVu); + } + } + + private static void appendArray(final Object o, final StringBuilder str, Set dejaVu, + final Class oClass) { + if (oClass == byte[].class) { + str.append(Arrays.toString((byte[]) o)); + } else if (oClass == short[].class) { + str.append(Arrays.toString((short[]) o)); + } else if (oClass == int[].class) { + str.append(Arrays.toString((int[]) o)); + } else if (oClass == long[].class) { + str.append(Arrays.toString((long[]) o)); + } else if (oClass == float[].class) { + str.append(Arrays.toString((float[]) o)); + } else if (oClass == double[].class) { + str.append(Arrays.toString((double[]) o)); + } else if (oClass == boolean[].class) { + str.append(Arrays.toString((boolean[]) o)); + } else if (oClass == char[].class) { + str.append(Arrays.toString((char[]) o)); + } else { + if (dejaVu == null) { + dejaVu = new HashSet<>(); + } + // special handling of container Object[] + final String id = identityToString(o); + if (dejaVu.contains(id)) { + str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); + } else { + dejaVu.add(id); + final Object[] oArray = (Object[]) o; + str.append('['); + boolean first = true; + for (final Object current : oArray) { + if (first) { + first = false; + } else { + str.append(", "); + } + recursiveDeepToString(current, str, new HashSet<>(dejaVu)); + } + str.append(']'); + } + //str.append(Arrays.deepToString((Object[]) o)); + } + } + + private static void appendMap(final Object o, final StringBuilder str, Set dejaVu) { + // special handling of container Map + if (dejaVu == null) { + dejaVu = new HashSet<>(); + } + final String id = identityToString(o); + if (dejaVu.contains(id)) { + str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); + } else { + dejaVu.add(id); + final Map oMap = (Map) o; + str.append('{'); + boolean isFirst = true; + for (final Object o1 : oMap.entrySet()) { + final Map.Entry current = (Map.Entry) o1; + if (isFirst) { + isFirst = false; + } else { + str.append(", "); + } + final Object key = current.getKey(); + final Object value = current.getValue(); + recursiveDeepToString(key, str, new HashSet<>(dejaVu)); + str.append('='); + recursiveDeepToString(value, str, new HashSet<>(dejaVu)); + } + str.append('}'); + } + } + + private static void appendCollection(final Object o, final StringBuilder str, Set dejaVu) { + // special handling of container Collection + if (dejaVu == null) { + dejaVu = new HashSet<>(); + } + final String id = identityToString(o); + if (dejaVu.contains(id)) { + str.append(RECURSION_PREFIX).append(id).append(RECURSION_SUFFIX); + } else { + dejaVu.add(id); + final Collection oCol = (Collection) o; + str.append('['); + boolean isFirst = true; + for (final Object anOCol : oCol) { + if (isFirst) { + isFirst = false; + } else { + str.append(", "); + } + recursiveDeepToString(anOCol, str, new HashSet<>(dejaVu)); + } + str.append(']'); + } + } + + private static void tryObjectToString(final Object o, final StringBuilder str) { + // it's just some other Object, we can only use toString(). + try { + str.append(o.toString()); + } catch (final Throwable t) { + handleErrorInObjectToString(o, str, t); + } + } + + private static void handleErrorInObjectToString(final Object o, final StringBuilder str, final Throwable t) { + str.append(ERROR_PREFIX); + str.append(identityToString(o)); + str.append(ERROR_SEPARATOR); + final String msg = t.getMessage(); + final String className = t.getClass().getName(); + str.append(className); + if (!className.equals(msg)) { + str.append(ERROR_MSG_SEPARATOR); + str.append(msg); + } + str.append(ERROR_SUFFIX); + } + + /** + * This method returns the same as if Object.toString() would not have been + * overridden in obj. + *

+ * Note that this isn't 100% secure as collisions can always happen with hash codes. + *

+ *

+ * Copied from Object.hashCode(): + *

+ *
+ * As much as is reasonably practical, the hashCode method defined by + * class {@code Object} does return distinct integers for distinct + * objects. (This is typically implemented by converting the internal + * address of the object into an integer, but this implementation + * technique is not required by the Java™ programming language.) + *
+ * + * @param obj the Object that is to be converted into an identity string. + * @return the identity string as also defined in Object.toString() + */ + static String identityToString(final Object obj) { + if (obj == null) { + return null; + } + return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj)); + } +} diff --git a/dao-support-lib/pom.xml b/dao-support-lib/pom.xml new file mode 100644 index 0000000..6ef8ca3 --- /dev/null +++ b/dao-support-lib/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + cn.axzo.foundation + axzo-lib-box + 2.0.0-SNAPSHOT + + + dao-support-lib + + + cn.axzo.foundation + common-lib + 2.0.0-SNAPSHOT + + + + + mysql + mysql-connector-java + provided + + + com.baomidou + mybatis-plus-boot-starter + provided + + + org.springframework.data + spring-data-commons + + + org.apache.commons + commons-lang3 + + + + com.alibaba + fastjson + + + + + org.springframework.boot + spring-boot-starter-test + test + + + junit-vintage-engine + org.junit.vintage + + + + + com.h2database + h2 + + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-api + test + + + + \ No newline at end of file diff --git a/dao-support-lib/src/main/java/cn/axzo/foundation/dao/support/data/page/PageResp.java b/dao-support-lib/src/main/java/cn/axzo/foundation/dao/support/data/page/PageResp.java new file mode 100644 index 0000000..9054fe4 --- /dev/null +++ b/dao-support-lib/src/main/java/cn/axzo/foundation/dao/support/data/page/PageResp.java @@ -0,0 +1,28 @@ +package cn.axzo.foundation.dao.support.data.page; + +import com.google.common.collect.ImmutableList; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Optional; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class PageResp { + /** + * 总数 + */ + private long total = 0; + private long size = 10; + private long current = 1; + private List data; + + public List getData() { + return Optional.ofNullable(data).orElse(ImmutableList.of()); + } +} diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..bce7319 --- /dev/null +++ b/deploy.sh @@ -0,0 +1 @@ +git pull && mvn clean install deploy -am -U -DskipTests -pl $1 \ No newline at end of file diff --git a/deploy_all.sh b/deploy_all.sh new file mode 100755 index 0000000..63d8f8d --- /dev/null +++ b/deploy_all.sh @@ -0,0 +1 @@ +git pull && mvn clean install deploy -am -U -DskipTests \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..de5a4ad --- /dev/null +++ b/pom.xml @@ -0,0 +1,81 @@ + + + + + cn.axzo.infra + axzo-parent + 2.4.13.4 + + + 4.0.0 + cn.axzo.foundation + axzo-lib-box + 2.0.0-SNAPSHOT + pom + + + 2.0.0-SNAPSHOT + 2.0.0-SNAPSHOT + + + + dao-support-lib + common-lib + + + + + org.projectlombok + lombok + + + com.google.guava + guava + + + + + + + cn.axzo.infra + axzo-bom + ${axzo-bom.version} + pom + import + + + cn.axzo.infra + axzo-dependencies + ${axzo-dependencies.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + + jar + + + + + + + + \ No newline at end of file