diff --git a/pom.xml b/pom.xml index f8516cf..d16ac36 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ xyz.zhouxy.plusone plusone-commons - 1.0.0-SNAPSHOT + 1.0.0-RC2 UTF-8 @@ -82,6 +82,20 @@ test + + org.mybatis + mybatis + 3.5.17 + test + + + + com.h2database + h2 + 2.2.224 + test + + com.fasterxml.jackson.core diff --git a/src/main/java/org/apache/ibatis/jdbc/AbstractSQL.java b/src/main/java/org/apache/ibatis/jdbc/AbstractSQL.java deleted file mode 100644 index 6e031c4..0000000 --- a/src/main/java/org/apache/ibatis/jdbc/AbstractSQL.java +++ /dev/null @@ -1,740 +0,0 @@ -/* - * Copyright 2009-2023 the original author or authors. - * - * Licensed 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 - * - * https://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 org.apache.ibatis.jdbc; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -/** - * @author Clinton Begin - * @author Jeff Butler - * @author Adam Gent - * @author Kazuki Shimizu - */ -public abstract class AbstractSQL { - - private static final String AND = ") \nAND ("; - private static final String OR = ") \nOR ("; - - private final SQLStatement sql = new SQLStatement(); - - public abstract T getSelf(); - - public T UPDATE(String table) { - sql().statementType = SQLStatement.StatementType.UPDATE; - sql().tables.add(table); - return getSelf(); - } - - public T SET(String sets) { - sql().sets.add(sets); - return getSelf(); - } - - /** - * Sets the. - * - * @param sets - * the sets - * - * @return the t - * - * @since 3.4.2 - */ - public T SET(String... sets) { - sql().sets.addAll(Arrays.asList(sets)); - return getSelf(); - } - - public T INSERT_INTO(String tableName) { - sql().statementType = SQLStatement.StatementType.INSERT; - sql().tables.add(tableName); - return getSelf(); - } - - public T VALUES(String columns, String values) { - INTO_COLUMNS(columns); - INTO_VALUES(values); - return getSelf(); - } - - /** - * Into columns. - * - * @param columns - * the columns - * - * @return the t - * - * @since 3.4.2 - */ - public T INTO_COLUMNS(String... columns) { - sql().columns.addAll(Arrays.asList(columns)); - return getSelf(); - } - - /** - * Into values. - * - * @param values - * the values - * - * @return the t - * - * @since 3.4.2 - */ - public T INTO_VALUES(String... values) { - List list = sql().valuesList.get(sql().valuesList.size() - 1); - Collections.addAll(list, values); - return getSelf(); - } - - public T SELECT(String columns) { - sql().statementType = SQLStatement.StatementType.SELECT; - sql().select.add(columns); - return getSelf(); - } - - /** - * Select. - * - * @param columns - * the columns - * - * @return the t - * - * @since 3.4.2 - */ - public T SELECT(String... columns) { - sql().statementType = SQLStatement.StatementType.SELECT; - sql().select.addAll(Arrays.asList(columns)); - return getSelf(); - } - - public T SELECT_DISTINCT(String columns) { - sql().distinct = true; - SELECT(columns); - return getSelf(); - } - - /** - * Select distinct. - * - * @param columns - * the columns - * - * @return the t - * - * @since 3.4.2 - */ - public T SELECT_DISTINCT(String... columns) { - sql().distinct = true; - SELECT(columns); - return getSelf(); - } - - public T DELETE_FROM(String table) { - sql().statementType = SQLStatement.StatementType.DELETE; - sql().tables.add(table); - return getSelf(); - } - - public T FROM(String table) { - sql().tables.add(table); - return getSelf(); - } - - /** - * From. - * - * @param tables - * the tables - * - * @return the t - * - * @since 3.4.2 - */ - public T FROM(String... tables) { - sql().tables.addAll(Arrays.asList(tables)); - return getSelf(); - } - - public T JOIN(String join) { - sql().join.add(join); - return getSelf(); - } - - /** - * Join. - * - * @param joins - * the joins - * - * @return the t - * - * @since 3.4.2 - */ - public T JOIN(String... joins) { - sql().join.addAll(Arrays.asList(joins)); - return getSelf(); - } - - public T INNER_JOIN(String join) { - sql().innerJoin.add(join); - return getSelf(); - } - - /** - * Inner join. - * - * @param joins - * the joins - * - * @return the t - * - * @since 3.4.2 - */ - public T INNER_JOIN(String... joins) { - sql().innerJoin.addAll(Arrays.asList(joins)); - return getSelf(); - } - - public T LEFT_OUTER_JOIN(String join) { - sql().leftOuterJoin.add(join); - return getSelf(); - } - - /** - * Left outer join. - * - * @param joins - * the joins - * - * @return the t - * - * @since 3.4.2 - */ - public T LEFT_OUTER_JOIN(String... joins) { - sql().leftOuterJoin.addAll(Arrays.asList(joins)); - return getSelf(); - } - - public T RIGHT_OUTER_JOIN(String join) { - sql().rightOuterJoin.add(join); - return getSelf(); - } - - /** - * Right outer join. - * - * @param joins - * the joins - * - * @return the t - * - * @since 3.4.2 - */ - public T RIGHT_OUTER_JOIN(String... joins) { - sql().rightOuterJoin.addAll(Arrays.asList(joins)); - return getSelf(); - } - - public T OUTER_JOIN(String join) { - sql().outerJoin.add(join); - return getSelf(); - } - - /** - * Outer join. - * - * @param joins - * the joins - * - * @return the t - * - * @since 3.4.2 - */ - public T OUTER_JOIN(String... joins) { - sql().outerJoin.addAll(Arrays.asList(joins)); - return getSelf(); - } - - public T WHERE(String conditions) { - sql().where.add(conditions); - sql().lastList = sql().where; - return getSelf(); - } - - /** - * Where. - * - * @param conditions - * the conditions - * - * @return the t - * - * @since 3.4.2 - */ - public T WHERE(String... conditions) { - sql().where.addAll(Arrays.asList(conditions)); - sql().lastList = sql().where; - return getSelf(); - } - - public T OR() { - sql().lastList.add(OR); - return getSelf(); - } - - public T AND() { - sql().lastList.add(AND); - return getSelf(); - } - - public T GROUP_BY(String columns) { - sql().groupBy.add(columns); - return getSelf(); - } - - /** - * Group by. - * - * @param columns - * the columns - * - * @return the t - * - * @since 3.4.2 - */ - public T GROUP_BY(String... columns) { - sql().groupBy.addAll(Arrays.asList(columns)); - return getSelf(); - } - - public T HAVING(String conditions) { - sql().having.add(conditions); - sql().lastList = sql().having; - return getSelf(); - } - - /** - * Having. - * - * @param conditions - * the conditions - * - * @return the t - * - * @since 3.4.2 - */ - public T HAVING(String... conditions) { - sql().having.addAll(Arrays.asList(conditions)); - sql().lastList = sql().having; - return getSelf(); - } - - public T ORDER_BY(String columns) { - sql().orderBy.add(columns); - return getSelf(); - } - - /** - * Order by. - * - * @param columns - * the columns - * - * @return the t - * - * @since 3.4.2 - */ - public T ORDER_BY(String... columns) { - sql().orderBy.addAll(Arrays.asList(columns)); - return getSelf(); - } - - /** - * Set the limit variable string(e.g. {@code "#{limit}"}). - * - * @param variable - * a limit variable string - * - * @return a self instance - * - * @see #OFFSET(String) - * - * @since 3.5.2 - */ - public T LIMIT(String variable) { - sql().limit = variable; - sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.OFFSET_LIMIT; - return getSelf(); - } - - /** - * Set the limit value. - * - * @param value - * an offset value - * - * @return a self instance - * - * @see #OFFSET(long) - * - * @since 3.5.2 - */ - public T LIMIT(int value) { - return LIMIT(String.valueOf(value)); - } - - /** - * Set the offset variable string(e.g. {@code "#{offset}"}). - * - * @param variable - * a offset variable string - * - * @return a self instance - * - * @see #LIMIT(String) - * - * @since 3.5.2 - */ - public T OFFSET(String variable) { - sql().offset = variable; - sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.OFFSET_LIMIT; - return getSelf(); - } - - /** - * Set the offset value. - * - * @param value - * an offset value - * - * @return a self instance - * - * @see #LIMIT(int) - * - * @since 3.5.2 - */ - public T OFFSET(long value) { - return OFFSET(String.valueOf(value)); - } - - /** - * Set the fetch first rows variable string(e.g. {@code "#{fetchFirstRows}"}). - * - * @param variable - * a fetch first rows variable string - * - * @return a self instance - * - * @see #OFFSET_ROWS(String) - * - * @since 3.5.2 - */ - public T FETCH_FIRST_ROWS_ONLY(String variable) { - sql().limit = variable; - sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.ISO; - return getSelf(); - } - - /** - * Set the fetch first rows value. - * - * @param value - * a fetch first rows value - * - * @return a self instance - * - * @see #OFFSET_ROWS(long) - * - * @since 3.5.2 - */ - public T FETCH_FIRST_ROWS_ONLY(int value) { - return FETCH_FIRST_ROWS_ONLY(String.valueOf(value)); - } - - /** - * Set the offset rows variable string(e.g. {@code "#{offset}"}). - * - * @param variable - * a offset rows variable string - * - * @return a self instance - * - * @see #FETCH_FIRST_ROWS_ONLY(String) - * - * @since 3.5.2 - */ - public T OFFSET_ROWS(String variable) { - sql().offset = variable; - sql().limitingRowsStrategy = SQLStatement.LimitingRowsStrategy.ISO; - return getSelf(); - } - - /** - * Set the offset rows value. - * - * @param value - * an offset rows value - * - * @return a self instance - * - * @see #FETCH_FIRST_ROWS_ONLY(int) - * - * @since 3.5.2 - */ - public T OFFSET_ROWS(long value) { - return OFFSET_ROWS(String.valueOf(value)); - } - - /** - * used to add a new inserted row while do multi-row insert. - * - * @return the t - * - * @since 3.5.2 - */ - public T ADD_ROW() { - sql().valuesList.add(new ArrayList<>()); - return getSelf(); - } - - private SQLStatement sql() { - return sql; - } - - public A usingAppender(A a) { - sql().sql(a); - return a; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sql().sql(sb); - return sb.toString(); - } - - private static class SafeAppendable { - private final Appendable appendable; - private boolean empty = true; - - public SafeAppendable(Appendable a) { - this.appendable = a; - } - - public SafeAppendable append(CharSequence s) { - try { - if (empty && s.length() > 0) { - empty = false; - } - appendable.append(s); - } catch (IOException e) { - throw new RuntimeException(e); - } - return this; - } - - public boolean isEmpty() { - return empty; - } - - } - - private static class SQLStatement { - - public enum StatementType { - - DELETE, - - INSERT, - - SELECT, - - UPDATE - - } - - private enum LimitingRowsStrategy { - NOP { - @Override - protected void appendClause(SafeAppendable builder, String offset, String limit) { - // NOP - } - }, - ISO { - @Override - protected void appendClause(SafeAppendable builder, String offset, String limit) { - if (offset != null) { - builder.append(" OFFSET ").append(offset).append(" ROWS"); - } - if (limit != null) { - builder.append(" FETCH FIRST ").append(limit).append(" ROWS ONLY"); - } - } - }, - OFFSET_LIMIT { - @Override - protected void appendClause(SafeAppendable builder, String offset, String limit) { - if (limit != null) { - builder.append(" LIMIT ").append(limit); - } - if (offset != null) { - builder.append(" OFFSET ").append(offset); - } - } - }; - - protected abstract void appendClause(SafeAppendable builder, String offset, String limit); - - } - - StatementType statementType; - List sets = new ArrayList<>(); - List select = new ArrayList<>(); - List tables = new ArrayList<>(); - List join = new ArrayList<>(); - List innerJoin = new ArrayList<>(); - List outerJoin = new ArrayList<>(); - List leftOuterJoin = new ArrayList<>(); - List rightOuterJoin = new ArrayList<>(); - List where = new ArrayList<>(); - List having = new ArrayList<>(); - List groupBy = new ArrayList<>(); - List orderBy = new ArrayList<>(); - List lastList = new ArrayList<>(); - List columns = new ArrayList<>(); - List> valuesList = new ArrayList<>(); - boolean distinct; - String offset; - String limit; - LimitingRowsStrategy limitingRowsStrategy = LimitingRowsStrategy.NOP; - - public SQLStatement() { - // Prevent Synthetic Access - valuesList.add(new ArrayList<>()); - } - - private void sqlClause(SafeAppendable builder, String keyword, List parts, String open, String close, - String conjunction) { - if (!parts.isEmpty()) { - if (!builder.isEmpty()) { - builder.append("\n"); - } - builder.append(keyword); - builder.append(" "); - builder.append(open); - String last = "________"; - for (int i = 0, n = parts.size(); i < n; i++) { - String part = parts.get(i); - if (i > 0 && !part.equals(AND) && !part.equals(OR) && !last.equals(AND) && !last.equals(OR)) { - builder.append(conjunction); - } - builder.append(part); - last = part; - } - builder.append(close); - } - } - - private String selectSQL(SafeAppendable builder) { - if (distinct) { - sqlClause(builder, "SELECT DISTINCT", select, "", "", ", "); - } else { - sqlClause(builder, "SELECT", select, "", "", ", "); - } - - sqlClause(builder, "FROM", tables, "", "", ", "); - joins(builder); - sqlClause(builder, "WHERE", where, "(", ")", " AND "); - sqlClause(builder, "GROUP BY", groupBy, "", "", ", "); - sqlClause(builder, "HAVING", having, "(", ")", " AND "); - sqlClause(builder, "ORDER BY", orderBy, "", "", ", "); - limitingRowsStrategy.appendClause(builder, offset, limit); - return builder.toString(); - } - - private void joins(SafeAppendable builder) { - sqlClause(builder, "JOIN", join, "", "", "\nJOIN "); - sqlClause(builder, "INNER JOIN", innerJoin, "", "", "\nINNER JOIN "); - sqlClause(builder, "OUTER JOIN", outerJoin, "", "", "\nOUTER JOIN "); - sqlClause(builder, "LEFT OUTER JOIN", leftOuterJoin, "", "", "\nLEFT OUTER JOIN "); - sqlClause(builder, "RIGHT OUTER JOIN", rightOuterJoin, "", "", "\nRIGHT OUTER JOIN "); - } - - private String insertSQL(SafeAppendable builder) { - sqlClause(builder, "INSERT INTO", tables, "", "", ""); - sqlClause(builder, "", columns, "(", ")", ", "); - for (int i = 0; i < valuesList.size(); i++) { - sqlClause(builder, i > 0 ? "," : "VALUES", valuesList.get(i), "(", ")", ", "); - } - return builder.toString(); - } - - private String deleteSQL(SafeAppendable builder) { - sqlClause(builder, "DELETE FROM", tables, "", "", ""); - sqlClause(builder, "WHERE", where, "(", ")", " AND "); - limitingRowsStrategy.appendClause(builder, null, limit); - return builder.toString(); - } - - private String updateSQL(SafeAppendable builder) { - sqlClause(builder, "UPDATE", tables, "", "", ""); - joins(builder); - sqlClause(builder, "SET", sets, "", "", ", "); - sqlClause(builder, "WHERE", where, "(", ")", " AND "); - limitingRowsStrategy.appendClause(builder, null, limit); - return builder.toString(); - } - - public String sql(Appendable a) { - SafeAppendable builder = new SafeAppendable(a); - if (statementType == null) { - return null; - } - - String answer; - - switch (statementType) { - case DELETE: - answer = deleteSQL(builder); - break; - - case INSERT: - answer = insertSQL(builder); - break; - - case SELECT: - answer = selectSQL(builder); - break; - - case UPDATE: - answer = updateSQL(builder); - break; - - default: - answer = null; - } - - return answer; - } - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/BoolRef.java b/src/main/java/xyz/zhouxy/plusone/commons/base/BoolRef.java deleted file mode 100644 index 56918ad..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/BoolRef.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.base; - -import com.google.common.annotations.Beta; - -import xyz.zhouxy.plusone.commons.function.BoolUnaryOperator; - -@Beta -public class BoolRef { - - private boolean value; - - public BoolRef(boolean value) { - this.value = value; - } - - public boolean getValue() { - return value; - } - - public void setValue(boolean value) { - this.value = value; - } - - public void apply(BoolUnaryOperator operator) { - this.value = operator.applyAsBool(this.value); - } - - @Override - public String toString() { - return String.format("BoolRef[%s]", value); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (value ? 1231 : 1237); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - return value == ((BoolRef) obj).value; - } - -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/CharRef.java b/src/main/java/xyz/zhouxy/plusone/commons/base/CharRef.java deleted file mode 100644 index 929d4b4..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/CharRef.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.base; - -import com.google.common.annotations.Beta; - -import xyz.zhouxy.plusone.commons.function.CharUnaryOperator; - -@Beta -public class CharRef { - - private char value; - - public CharRef(char value) { - this.value = value; - } - - public char getValue() { - return value; - } - - public void setValue(char value) { - this.value = value; - } - - public void apply(CharUnaryOperator operator) { - this.value = operator.applyAsChar(this.value); - } - - @Override - public String toString() { - return String.format("CharRef[%s]", value); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + value; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - return value == ((CharRef) obj).value; - } - -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/DoubleRef.java b/src/main/java/xyz/zhouxy/plusone/commons/base/DoubleRef.java deleted file mode 100644 index 9c476e5..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/DoubleRef.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.base; - -import java.util.function.DoubleUnaryOperator; - -import com.google.common.annotations.Beta; - -@Beta -public class DoubleRef { - - private double value; - - public DoubleRef(double value) { - this.value = value; - } - - public double getValue() { - return value; - } - - public void setValue(double value) { - this.value = value; - } - - public void apply(DoubleUnaryOperator operator) { - this.value = operator.applyAsDouble(this.value); - } - - @Override - public String toString() { - return String.format("DoubleRef[%s]", value); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - long temp; - temp = Double.doubleToLongBits(value); - result = prime * result + (int) (temp ^ (temp >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - final DoubleRef other = (DoubleRef) obj; - return Double.doubleToLongBits(value) == Double.doubleToLongBits(other.value); - } - -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/IWithCode.java b/src/main/java/xyz/zhouxy/plusone/commons/base/IWithCode.java index 015fdc3..5633567 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/IWithCode.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/base/IWithCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package xyz.zhouxy.plusone.commons.base; import java.util.Objects; import javax.annotation.Nonnull; +import javax.annotation.Nullable; /** * 规定实现类带有 {@code getCode} 方法。 @@ -31,7 +32,19 @@ public interface IWithCode { @Nonnull T getCode(); - default boolean equalsCode(T code) { + default boolean isCodeEquals(@Nullable T code) { return Objects.equals(getCode(), code); } + + default boolean isSameCodeAs(@Nullable IWithCode other) { + return other != null && Objects.equals(getCode(), other.getCode()); + } + + default boolean isSameCodeAs(@Nullable IWithIntCode other) { + return other != null && Objects.equals(getCode(), other.getCode()); + } + + default boolean isSameCodeAs(@Nullable IWithLongCode other) { + return other != null && Objects.equals(getCode(), other.getCode()); + } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/IWithIntCode.java b/src/main/java/xyz/zhouxy/plusone/commons/base/IWithIntCode.java index 7deac35..3e881cc 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/IWithIntCode.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/base/IWithIntCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package xyz.zhouxy.plusone.commons.base; +import java.util.Objects; + +import javax.annotation.Nullable; + /** * 规定实现类带有 {@code getCode} 方法。 * 用于像自定义异常等需要带有 {@code code} 字段的类, @@ -26,7 +30,19 @@ package xyz.zhouxy.plusone.commons.base; public interface IWithIntCode { int getCode(); - default boolean equalsCode(int code) { + default boolean isCodeEquals(int code) { return getCode() == code; } + + default boolean isSameCodeAs(@Nullable IWithCode other) { + return other != null && Objects.equals(getCode(), other.getCode()); + } + + default boolean isSameCodeAs(@Nullable IWithIntCode other) { + return other != null && getCode() == other.getCode(); + } + + default boolean isSameCodeAs(@Nullable IWithLongCode other) { + return other != null && getCode() == other.getCode(); + } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/IWithLongCode.java b/src/main/java/xyz/zhouxy/plusone/commons/base/IWithLongCode.java index 1bd8d20..b10d8a2 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/IWithLongCode.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/base/IWithLongCode.java @@ -1,5 +1,5 @@ /* - * Copyright 2022-2024 the original author or authors. + * Copyright 2022-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,10 @@ package xyz.zhouxy.plusone.commons.base; +import java.util.Objects; + +import javax.annotation.Nullable; + /** * 规定实现类带有 {@code getCode} 方法。 * 用于像自定义异常等需要带有 {@code code} 字段的类, @@ -26,7 +30,19 @@ package xyz.zhouxy.plusone.commons.base; public interface IWithLongCode { long getCode(); - default boolean equalsCode(long code) { + default boolean isCodeEquals(long code) { return getCode() == code; } + + default boolean isSameCodeAs(@Nullable IWithCode other) { + return other != null && Objects.equals(getCode(), other.getCode()); + } + + default boolean isSameCodeAs(@Nullable IWithIntCode other) { + return other != null && getCode() == other.getCode(); + } + + default boolean isSameCodeAs(@Nullable IWithLongCode other) { + return other != null && getCode() == other.getCode(); + } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/IntRef.java b/src/main/java/xyz/zhouxy/plusone/commons/base/IntRef.java deleted file mode 100644 index 8f0148b..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/IntRef.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.base; - -import java.util.function.IntUnaryOperator; - -import com.google.common.annotations.Beta; - -@Beta -public class IntRef { - - private int value; - - public IntRef(int value) { - this.value = value; - } - - public int getValue() { - return value; - } - - public void setValue(int value) { - this.value = value; - } - - public void apply(IntUnaryOperator operator) { - this.value = operator.applyAsInt(this.value); - } - - @Override - public String toString() { - return String.format("IntRef[%s]", value); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + value; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - return value == ((IntRef) obj).value; - } - -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/JRE.java b/src/main/java/xyz/zhouxy/plusone/commons/base/JRE.java deleted file mode 100644 index 957d309..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/JRE.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.base; - -import xyz.zhouxy.plusone.commons.util.StringTools; - -/** - * JRE version - */ -public class JRE { - - private static final int JAVA_8 = 8; - - public static final int CURRENT_VERSION = getJre(); - - public static boolean isJava8() { - return CURRENT_VERSION == JAVA_8; - } - - private static int getJre() { - String version = System.getProperty("java.version"); - boolean isNotBlank = StringTools.isNotBlank(version); - if (isNotBlank && version.startsWith("1.8")) { - return JAVA_8; - } - // if JRE version is 9 or above, we can get version from - // java.lang.Runtime.version() - try { - return getMajorVersion(version); - } catch (Exception e) { - // assuming that JRE version is 8. - } - // default java 8 - return JAVA_8; - } - - private static int getMajorVersion(String version) { - if (version.startsWith("1.")) { - return Integer.parseInt(version.substring(2, 3)); - } else { - int dotIndex = version.indexOf("."); - return (dotIndex != -1) ? Integer.parseInt(version.substring(0, dotIndex)) : Integer.parseInt(version); - } - } - - private JRE() { - throw new IllegalStateException("Utility class"); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/LongRef.java b/src/main/java/xyz/zhouxy/plusone/commons/base/LongRef.java deleted file mode 100644 index 7d079bd..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/LongRef.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.base; - -import java.util.function.LongUnaryOperator; - -import com.google.common.annotations.Beta; - -@Beta -public class LongRef { - - private long value; - - public LongRef(long value) { - this.value = value; - } - - public long getValue() { - return value; - } - - public void setValue(long value) { - this.value = value; - } - - public void apply(LongUnaryOperator operator) { - this.value = operator.applyAsLong(this.value); - } - - @Override - public String toString() { - return String.format("LongRef[%s]", value); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (int) (value ^ (value >>> 32)); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - return value == ((LongRef) obj).value; - } - -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/Ref.java b/src/main/java/xyz/zhouxy/plusone/commons/base/Ref.java index 94c5ee4..e5243b4 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/base/Ref.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/base/Ref.java @@ -18,35 +18,100 @@ package xyz.zhouxy.plusone.commons.base; import java.util.Objects; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.UnaryOperator; -import com.google.common.annotations.Beta; +import javax.annotation.Nullable; -@Beta +/** + * {@link Ref} 包装了一个值,表示对该值的应用。 + * + *

灵感来自于 C# 的 {@value ref} 参数修饰符。C# 允许通过以下方式,将值返回给调用端:

+ *
+ * void Method(ref int refArgument)
+ * {
+ *     refArgument = refArgument + 44;
+ * }
+ *
+ * int number = 1;
+ * Method(ref number);
+ * Console.WriteLine(number); // Output: 45
+ * 
+ * {@link Ref} 使 Java 可以达到类似的效果,如: + *
+ * void method(final Ref<Integer> refArgument) {
+ *     refArgument.transformValue(i -> i + 44);
+ * }
+ *
+ * Ref<Integer> number = Ref.of(1);
+ * method(number);
+ * System.out.println(number.getValue()); // Output: 45
+ * 
+ *

+ * 当一个方法需要产生多个结果时,无法有多个返回值,可以使用 {@link Ref} 作为参数传入,方法内部修改 {@link Ref} 的值。 + * 调用方在调用方法之后,使用 {@code getValue()} 获取结果。 + *

+ *
+ * String method(final Ref<Integer> intRefArgument, final Ref<String> strRefArgument) {
+ *     intRefArgument.transformValue(i -> i + 44);
+ *     strRefArgument.setValue("Hello " + strRefArgument.getValue());
+ *     return "Return string";
+ * }
+ *
+ * Ref<Integer> number = Ref.of(1);
+ * Ref<String> str = Ref.of("Java");
+ * String result = method(number, str);
+ * System.out.println(number.getValue()); // Output: 45
+ * System.out.println(str.getValue()); // Output: Hello Java
+ * System.out.println(result); // Output: Return string
+ * 
+ * + * @author
ZhouXY + * @since 1.0.0 + */ public final class Ref { + @Nullable private T value; - public Ref() { - this.value = null; - } - - public Ref(T value) { + private Ref(@Nullable T value) { this.value = value; } + public static Ref of(@Nullable T value) { + return new Ref<>(value); + } + + public static Ref empty() { + return new Ref<>(null); + } + + @Nullable public T getValue() { return value; } - public void setValue(T value) { + public void setValue(@Nullable T value) { this.value = value; } - public void transform(UnaryOperator operator) { + public void transformValue(UnaryOperator operator) { this.value = operator.apply(this.value); } + public Ref transform(Function function) { + return Ref.of(function.apply(this.value)); + } + + public boolean checkValue(Predicate predicate) { + return predicate.test(this.value); + } + + public void execute(Consumer consumer) { + consumer.accept(value); + } + public boolean isNull() { return this.value == null; } @@ -55,10 +120,6 @@ public final class Ref { return this.value != null; } - public void execute(Consumer consumer) { - consumer.accept(value); - } - @Override public String toString() { return String.format("Ref[%s]", value); @@ -73,7 +134,7 @@ public final class Ref { } @Override - public boolean equals(Object obj) { + public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (obj == null) diff --git a/src/main/java/xyz/zhouxy/plusone/commons/base/package-info.java b/src/main/java/xyz/zhouxy/plusone/commons/base/package-info.java new file mode 100644 index 0000000..523b74d --- /dev/null +++ b/src/main/java/xyz/zhouxy/plusone/commons/base/package-info.java @@ -0,0 +1,75 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * 基础组件 + * + *

Ref

+ *

+ * {@link Ref} 包装了一个值,表示对该值的应用。 + *

+ *

灵感来自于 C# 的 {@value ref} 参数修饰符。C# 允许通过以下方式,将值返回给调用端:

+ *
+ * void Method(ref int refArgument)
+ * {
+ *     refArgument = refArgument + 44;
+ * }
+ *
+ * int number = 1;
+ * Method(ref number);
+ * Console.WriteLine(number); // Output: 45
+ * 
+ * {@link Ref} 使 Java 可以达到类似的效果,如: + *
+ * void method(Ref<Integer> refArgument) {
+ *     refArgument.transformValue(i -> i + 44);
+ * }
+ *
+ * Ref<Integer> number = Ref.of(1);
+ * method(number);
+ * System.out.println(number.getValue()); // Output: 45
+ * 
+ *

+ * 当一个方法需要产生多个结果时,无法有多个返回值,可以使用 {@link Ref} 作为参数传入,方法内部修改 {@link Ref} 的值。 + * 调用方在调用方法之后,使用 {@code getValue()} 获取结果。 + *

+ *
+ * String method(Ref<Integer> intRefArgument, Ref<String> strRefArgument) {
+ *     intRefArgument.transformValue(i -> i + 44);
+ *     strRefArgument.setValue("Hello " + strRefArgument.getValue());
+ *     return "Return string";
+ * }
+ *
+ * Ref<Integer> number = Ref.of(1);
+ * Ref<String> str = Ref.of("Java");
+ * String result = method(number, str);
+ * System.out.println(number.getValue()); // Output: 45
+ * System.out.println(str.getValue()); // Output: Hello Java
+ * System.out.println(result); // Output: Return string
+ * 
+ * + *

IWithCode

+ *

+ * 类似于枚举之类的类,通常需要设置固定的码值表示对应的含义。 + * 可实现 {@link IWithCode}、{@link IWithIntCode}、{@link IWithLongCode},便于在需要的地方对这些接口的实现进行处理。 + *

+ */ +@CheckReturnValue +@ParametersAreNonnullByDefault +package xyz.zhouxy.plusone.commons.base; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.annotation.CheckReturnValue; diff --git a/src/main/java/xyz/zhouxy/plusone/commons/collection/AbstractMapWrapper.java b/src/main/java/xyz/zhouxy/plusone/commons/collection/AbstractMapWrapper.java deleted file mode 100644 index f8a9d21..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/collection/AbstractMapWrapper.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.collection; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Consumer; -import java.util.function.Function; - -import javax.annotation.Nullable; - -import com.google.common.annotations.Beta; - -import xyz.zhouxy.plusone.commons.util.ConcurrentHashMapTools; - -@Beta -public abstract class AbstractMapWrapper> { - - private final Map map; - private final Consumer keyChecker; - private final Consumer valueChecker; - - protected AbstractMapWrapper(Map map, @Nullable Consumer keyChecker, @Nullable Consumer valueChecker) { - this.map = map; - this.keyChecker = keyChecker; - this.valueChecker = valueChecker; - } - - public final T put(K key, V value) { - if (this.keyChecker != null) { - this.keyChecker.accept(key); - } - if (this.valueChecker != null) { - this.valueChecker.accept(value); - } - this.map.put(key, value); - return getSelf(); - } - - public final T putAll(Map m) { - m.forEach(this::put); - return getSelf(); - } - - /** - * 获取 {@code map} 中的值。如果 {@code key} 不存在,则抛出异常。 - * 将 {@code value}(可为 {@code null})装进 {@link Optional} 中后返回。 - * 为了这碟醋包的这盘饺子。 - * - * @param key 键 - * @return 可缺失的值 - * @throws IllegalArgumentException key 不存在时抛出。 - */ - public Optional get(K key) { - if (!this.map.containsKey(key)) { - throw new IllegalArgumentException("Key does not exist"); - } - return Optional.ofNullable(this.map.get(key)); - } - - @SuppressWarnings("unchecked") - public final Optional getAndConvert(K key) { - return get(key).map(v -> (R) v); - } - - public final Optional getAndConvert(K key, Function mapper) { - return get(key).map(mapper); - } - - public final boolean containsKey(Object key) { - return this.map.containsKey(key); - } - - public final int size() { - return this.map.size(); - } - - public final Set keySet() { - return this.map.keySet(); - } - - public final Collection values() { - return this.map.values(); - } - - public final Set> entrySet() { - return this.map.entrySet(); - } - - public final void clear() { - this.map.clear(); - } - - public final boolean containsValue(Object value) { - return this.map.containsValue(value); - } - - public final boolean isEmpty() { - return this.map.isEmpty(); - } - - public final V remove(Object key) { - return this.map.remove(key); - } - - public final V putIfAbsent(K key, V value) { - if (this.keyChecker != null) { - this.keyChecker.accept(key); - } - if (this.valueChecker != null) { - this.valueChecker.accept(value); - } - return this.map.putIfAbsent(key, value); - } - - public final V computeIfAbsent(K key, Function mappingFunction) { - if (this.keyChecker != null) { - this.keyChecker.accept(key); - } - Function func = (K k) -> { - V value = mappingFunction.apply(k); - if (this.valueChecker != null) { - this.valueChecker.accept(value); - } - return value; - }; - if (this.map instanceof ConcurrentHashMap) { - return ConcurrentHashMapTools.computeIfAbsent( - (ConcurrentHashMap) this.map, key, func); - } else { - return this.map.computeIfAbsent(key, func); - } - } - - public final Map exportMap() { - return this.map; - } - - public final Map exportUnmodifiableMap() { - return Collections.unmodifiableMap(this.map); - } - - protected abstract T getSelf(); - - @Override - public String toString() { - return this.map.toString(); - } - - public abstract static class Builder> { - protected final Map map; - protected Consumer keyChecker; - protected Consumer valueChecker; - - protected Builder(Map map) { - this.map = map; - } - - public Builder keyChecker(@Nullable Consumer keyChecker) { - this.keyChecker = keyChecker; - return this; - } - - public Builder valueChecker(@Nullable Consumer valueChecker) { - this.valueChecker = valueChecker; - return this; - } - - public Builder put(K key, V value) { - if (this.keyChecker != null) { - this.keyChecker.accept(key); - } - if (this.valueChecker != null) { - this.valueChecker.accept(value); - } - this.map.put(key, value); - return this; - } - - public Builder putAll(Map m) { - m.forEach(this::put); - return this; - } - - public abstract T build(); - - public abstract T buildUnmodifiableMap(); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/collection/CollectionTools.java b/src/main/java/xyz/zhouxy/plusone/commons/collection/CollectionTools.java index 446ab37..f5e671b 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/collection/CollectionTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/collection/CollectionTools.java @@ -17,12 +17,24 @@ package xyz.zhouxy.plusone.commons.collection; import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +/** + * 集合工具类 + * + * @author ZhouXY + * @since 0.1.0 + */ public class CollectionTools { + // TODO [添加] 新增其它集合类型,如 guava 的扩展集合等 + // isEmpty public static boolean isEmpty(@Nullable Collection collection) { @@ -43,6 +55,21 @@ public class CollectionTools { return map != null && !map.isEmpty(); } + @Nonnull + public static List nullToEmptyList(@Nullable List list) { + return list == null ? Collections.emptyList() : list; + } + + @Nonnull + public static Set nullToEmptySet(@Nullable Set set) { + return set == null ? Collections.emptySet() : set; + } + + @Nonnull + public static Map nullToEmptyMap(@Nullable Map map) { + return map == null ? Collections.emptyMap() : map; + } + private CollectionTools() { throw new IllegalStateException("Utility class"); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/collection/MapWrapper.java b/src/main/java/xyz/zhouxy/plusone/commons/collection/MapWrapper.java deleted file mode 100644 index c928ae6..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/collection/MapWrapper.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.collection; - -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.function.Consumer; - -import com.google.common.annotations.Beta; - -@Beta -public final class MapWrapper extends AbstractMapWrapper> { - - private MapWrapper(Map map, Consumer keyChecker, Consumer valueChecker) { - super(map, keyChecker, valueChecker); - } - - public static Builder wrap(Map map) { - return new Builder<>(map); - } - - public static Builder wrapHashMap() { - return new Builder<>(new HashMap<>()); - } - - public static Builder wrapHashMap(int initialCapacity) { - return new Builder<>(new HashMap<>(initialCapacity)); - } - - public static Builder wrapHashMap(int initialCapacity, float loadFactor) { - return new Builder<>(new HashMap<>(initialCapacity, loadFactor)); - } - - public static , V> Builder wrapTreeMap() { - return new Builder<>(new TreeMap<>()); - } - - public static Builder wrapTreeMap(SortedMap m) { - return new Builder<>(new TreeMap<>(m)); - } - - public static Builder wrapTreeMap(Comparator comparator) { - return new Builder<>(new TreeMap<>(comparator)); - } - - public static final class Builder extends AbstractMapWrapper.Builder> { - - private Builder(Map map) { - super(map); - } - - @Override - public MapWrapper build() { - return new MapWrapper<>(map, keyChecker, valueChecker); - } - - @Override - public MapWrapper buildUnmodifiableMap() { - return new MapWrapper<>(Collections.unmodifiableMap(map), keyChecker, valueChecker); - } - } - - @Override - protected MapWrapper getSelf() { - return this; - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/collection/ReadWriteLockedTable.java b/src/main/java/xyz/zhouxy/plusone/commons/collection/ReadWriteLockedTable.java deleted file mode 100644 index a89138e..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/collection/ReadWriteLockedTable.java +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.collection; - -import com.google.common.annotations.Beta; -import com.google.common.collect.ImmutableTable; -import com.google.common.collect.Table; -import xyz.zhouxy.plusone.commons.annotation.ReaderMethod; -import xyz.zhouxy.plusone.commons.annotation.WriterMethod; - -import javax.annotation.CheckForNull; -import javax.annotation.Nonnull; -import javax.annotation.concurrent.ThreadSafe; -import java.util.Collection; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -/** - * 使用 {@link ReentrantReadWriteLock} 将 {@link Table} 包装为线程安全的集合。 - * - *

- * 可通过以下方式构建一个线程安全的 {@link Table} - *

- * - *
- * LockedTable.of(HashBasedTable.create())
- * 
- * - *

- * NOTE: 如果 {@link Table} 不需要更改,请使用 {@link ImmutableTable} - *

- * - * @author ZhouXY - * @see Table - * @see ImmutableTable - * @see ReentrantReadWriteLock - * @since 0.1.0-SNAPSHOT - */ -@Beta -@ThreadSafe -public class ReadWriteLockedTable implements Table { - - private final Table table; - - private final ReentrantReadWriteLock.ReadLock readLock; - private final ReentrantReadWriteLock.WriteLock writeLock; - - private ReadWriteLockedTable(Table table, boolean fair) { - this.table = Objects.requireNonNull(table); - ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(fair); - this.readLock = rwl.readLock(); - this.writeLock = rwl.writeLock(); - } - - public static ReadWriteLockedTable of(Table table) { - if (table instanceof ReadWriteLockedTable) { - return (ReadWriteLockedTable) table; - } else { - return new ReadWriteLockedTable<>(table, false); - } - } - - public static ReadWriteLockedTable of(Table table, boolean fair) { - if (table instanceof ReadWriteLockedTable) { - return (ReadWriteLockedTable) table; - } else { - return new ReadWriteLockedTable<>(table, fair); - } - } - - @Override - @ReaderMethod - public boolean contains(@CheckForNull @Nonnull Object rowKey, @CheckForNull @Nonnull Object columnKey) { - readLock.lock(); - try { - return this.table.contains(rowKey, columnKey); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public boolean containsRow(@CheckForNull @Nonnull Object rowKey) { - readLock.lock(); - try { - return this.table.containsRow(rowKey); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public boolean containsColumn(@CheckForNull @Nonnull Object columnKey) { - readLock.lock(); - try { - return this.table.containsColumn(columnKey); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public boolean containsValue(@CheckForNull @Nonnull Object value) { - readLock.lock(); - try { - return this.table.containsValue(value); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public V get(@CheckForNull @Nonnull Object rowKey, @CheckForNull @Nonnull Object columnKey) { - readLock.lock(); - try { - return this.table.get(rowKey, columnKey); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public boolean isEmpty() { - readLock.lock(); - try { - return this.table.isEmpty(); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public int size() { - readLock.lock(); - try { - return this.table.size(); - } finally { - readLock.unlock(); - } - } - - @Override - @WriterMethod - public void clear() { - writeLock.lock(); - try { - this.table.clear(); - } finally { - writeLock.unlock(); - } - } - - @Override - @WriterMethod - public V put(@CheckForNull @Nonnull R rowKey, - @CheckForNull @Nonnull C columnKey, - @CheckForNull @Nonnull V value) { - writeLock.lock(); - try { - return this.table.put(rowKey, columnKey, value); - } finally { - writeLock.unlock(); - } - } - - @Override - @WriterMethod - public void putAll(@Nonnull Table table) { - writeLock.lock(); - try { - this.table.putAll(table); - } finally { - writeLock.unlock(); - } - } - - @Override - @WriterMethod - public V remove(@CheckForNull @Nonnull Object rowKey, @CheckForNull @Nonnull Object columnKey) { - writeLock.lock(); - try { - return this.table.remove(rowKey, columnKey); - } finally { - writeLock.unlock(); - } - } - - @Override - @ReaderMethod - public Map row(@CheckForNull @Nonnull R rowKey) { - readLock.lock(); - try { - return this.table.row(rowKey); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Map column(@CheckForNull @Nonnull C columnKey) { - readLock.lock(); - try { - return this.table.column(columnKey); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Set> cellSet() { - readLock.lock(); - try { - return this.table.cellSet(); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Set rowKeySet() { - readLock.lock(); - try { - return this.table.rowKeySet(); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Set columnKeySet() { - readLock.lock(); - try { - return this.table.columnKeySet(); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Collection values() { - readLock.lock(); - try { - return this.table.values(); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Map> rowMap() { - readLock.lock(); - try { - return this.table.rowMap(); - } finally { - readLock.unlock(); - } - } - - @Override - @ReaderMethod - public Map> columnMap() { - readLock.lock(); - try { - return this.table.columnMap(); - } finally { - readLock.unlock(); - } - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/collection/SafeConcurrentHashMap.java b/src/main/java/xyz/zhouxy/plusone/commons/collection/SafeConcurrentHashMap.java deleted file mode 100644 index 0b60882..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/collection/SafeConcurrentHashMap.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.collection; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import javax.annotation.concurrent.ThreadSafe; - -import xyz.zhouxy.plusone.commons.base.JRE; -import xyz.zhouxy.plusone.commons.util.ConcurrentHashMapTools; - -/** - * SafeConcurrentHashMap - * - *

- * Java 8 的 {@link ConcurrentHashMap#computeIfAbsent(Object, Function)} 方法有 bug, - * 使用 Java 8 时,可使用这个类进行替换。 - * - * @author ZhouXY - * @since 1.0 - * @see ConcurrentHashMap - * @see ConcurrentHashMapTools#computeIfAbsentForJava8(ConcurrentHashMap, Object, Function) - */ -@ThreadSafe -public class SafeConcurrentHashMap extends ConcurrentHashMap { - - private static final long serialVersionUID = 4352954948768449595L; - - /** - * Creates a new, empty map with the default initial table size (16). - */ - public SafeConcurrentHashMap() { - } - - /** - * Creates a new, empty map with an initial table size - * accommodating the specified number of elements without the need - * to dynamically resize. - * - * @param initialCapacity The implementation performs internal - * sizing to accommodate this many elements. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative - */ - public SafeConcurrentHashMap(int initialCapacity) { - super(initialCapacity); - } - - /** - * Creates a new map with the same mappings as the given map. - * - * @param m the map - */ - public SafeConcurrentHashMap(Map m) { - super(m); - } - - /** - * Creates a new, empty map with an initial table size based on - * the given number of elements ({@code initialCapacity}) and - * initial table density ({@code loadFactor}). - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @throws IllegalArgumentException if the initial capacity of - * elements is negative or the load factor is nonpositive - * @since 1.6 - */ - public SafeConcurrentHashMap(int initialCapacity, float loadFactor) { - super(initialCapacity, loadFactor); - } - - /** - * Creates a new, empty map with an initial table size based on - * the given number of elements ({@code initialCapacity}), table - * density ({@code loadFactor}), and number of concurrently - * updating threads ({@code concurrencyLevel}). - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements, - * given the specified load factor. - * @param loadFactor the load factor (table density) for - * establishing the initial table size - * @param concurrencyLevel the estimated number of concurrently - * updating threads. The implementation may use this value as - * a sizing hint. - * @throws IllegalArgumentException if the initial capacity is - * negative or the load factor or concurrencyLevel are - * nonpositive - */ - public SafeConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { - super(initialCapacity, loadFactor, concurrencyLevel); - } - - /** {@inheritDoc} */ - @Override - public V computeIfAbsent(K key, Function mappingFunction) { - return JRE.isJava8() - ? ConcurrentHashMapTools.computeIfAbsentForJava8(this, key, mappingFunction) - : super.computeIfAbsent(key, mappingFunction); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java b/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java index aa434c6..4d8e03b 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/constant/PatternConsts.java @@ -22,23 +22,76 @@ import java.util.regex.Pattern; * 正则表达式常量 * * @author ZhouXY + * @see RegexConsts + * @see xyz.zhouxy.plusone.commons.util.RegexTools */ public final class PatternConsts { - public static final Pattern DATE = Pattern.compile(RegexConsts.DATE); + /** + * yyyyMMdd + * + * @see RegexConsts#BASIC_ISO_DATE + *

+ */ + public static final Pattern BASIC_ISO_DATE = Pattern.compile(RegexConsts.BASIC_ISO_DATE); + /** + * yyyy-MM-dd + * + * @see RegexConsts#ISO_LOCAL_DATE + */ + public static final Pattern ISO_LOCAL_DATE = Pattern.compile(RegexConsts.ISO_LOCAL_DATE); + + /** + * 密码 + * + * @see RegexConsts#PASSWORD + */ public static final Pattern PASSWORD = Pattern.compile(RegexConsts.PASSWORD); + /** + * 验证码 + * + * @see RegexConsts#CAPTCHA + */ public static final Pattern CAPTCHA = Pattern.compile(RegexConsts.CAPTCHA); + /** + * 邮箱地址 + * + * @see RegexConsts#EMAIL + */ public static final Pattern EMAIL = Pattern.compile(RegexConsts.EMAIL); + /** + * 中国大陆手机号 + * + * @see RegexConsts#MOBILE_PHONE + */ public static final Pattern MOBILE_PHONE = Pattern.compile(RegexConsts.MOBILE_PHONE); + /** + * 用户名 + * + * @see RegexConsts#USERNAME + */ public static final Pattern USERNAME = Pattern.compile(RegexConsts.USERNAME); + /** + * 昵称 + * + * @see RegexConsts#NICKNAME + */ public static final Pattern NICKNAME = Pattern.compile(RegexConsts.NICKNAME); + /** + * 中国第二代居民身份证 + * + * @see RegexConsts#CHINESE_2ND_ID_CARD_NUMBER + */ + public static final Pattern CHINESE_2ND_ID_CARD_NUMBER + = Pattern.compile(RegexConsts.CHINESE_2ND_ID_CARD_NUMBER, Pattern.CASE_INSENSITIVE); + private PatternConsts() { throw new IllegalStateException("Utility class"); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java b/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java index 64e3bea..9730ce1 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/constant/RegexConsts.java @@ -20,22 +20,30 @@ package xyz.zhouxy.plusone.commons.constant; * 正则表达式常量 * * @author ZhouXY + * @see PatternConsts */ public final class RegexConsts { - public static final String DATE = "^\\d{4}-\\d{2}-\\d{2}"; + public static final String BASIC_ISO_DATE = "^(?\\d{4,9})(?\\d{2})(?
\\d{2})"; + + public static final String ISO_LOCAL_DATE = "^(?\\d{4,9})-(?\\d{2})-(?
\\d{2})"; public static final String PASSWORD = "^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])[\\w\\\\!#$%&'*\\+\\-/=?^`{|}~@\\(\\)\\[\\]\",\\.;':><]{8,32}$"; - public static final String CAPTCHA = "^[0-9A-Za-z]{4,6}$"; + public static final String CAPTCHA = "^\\w{4,6}$"; - public static final String EMAIL = "^\\w+([-+.]\\w+)*@[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})*(\\.(?![0-9]+$)[a-zA-Z0-9][-0-9A-Za-z]{0,62})$"; + public static final String EMAIL + = "(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")" + + "@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"; public static final String MOBILE_PHONE = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$"; - public static final String USERNAME = "^[\\da-zA-Z_.@\\\\]{4,36}$"; + public static final String USERNAME = "^[\\w-_.@]{4,36}$"; - public static final String NICKNAME = "^[\\da-zA-Z_.@\\\\]{4,36}$"; + public static final String NICKNAME = "^[\\w-_.@]{4,36}$"; + + public static final String CHINESE_2ND_ID_CARD_NUMBER + = "^(?(?(?\\d{2})\\d{2})\\d{2})(?\\d{8})\\d{2}(?\\d)([\\dX])$"; private RegexConsts() { throw new IllegalStateException("Utility class"); diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/DataNotExistsException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/DataNotExistsException.java new file mode 100644 index 0000000..5877a68 --- /dev/null +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/DataNotExistsException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.exception; + +/** + * 数据不存在异常 + * + * @author ZhouXY + * @since 0.1.0 + */ +public final class DataNotExistsException extends Exception { + + private static final long serialVersionUID = 6536955800679703111L; + + public DataNotExistsException() { + super(); + } + + public DataNotExistsException(String message) { + super(message); + } + + public DataNotExistsException(Throwable cause) { + super(cause); + } + + public DataNotExistsException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/ExceptionType.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/ExceptionType.java new file mode 100644 index 0000000..f8ad83b --- /dev/null +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/ExceptionType.java @@ -0,0 +1,130 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.exception; + +import javax.annotation.Nonnull; + +import xyz.zhouxy.plusone.commons.base.IWithCode; + +/** + * 异常类型 + * + *

+ * 异常在不同场景下被抛出,可以用不同的枚举值,表示不同的异常类型。 + * 该枚举实现本接口,用于基于不同类型创建异常。 + * + *

+ * public final class LoginException extends RuntimeException {
+ *     private final Type type;
+ *     private LoginException(Type type, String message) {
+ *         super(message);
+ *         this.type = type;
+ *     }
+ *
+ *     private LoginException(Type type, Throwable cause) {
+ *         super(cause);
+ *         this.type = type;
+ *     }
+ *
+ *     private LoginException(Type type, String message, Throwable cause) {
+ *         super(message, cause);
+ *         this.type = type;
+ *     }
+ *
+ *     // ...
+ *
+ *     public enum Type implements ExceptionType {
+ *         DEFAULT("00", "当前会话未登录"),
+ *         NOT_TOKEN("10", "未提供token"),
+ *         INVALID_TOKEN("20", "token无效"),
+ *         TOKEN_TIMEOUT("30", "token已过期"),
+ *         BE_REPLACED("40", "token已被顶下线"),
+ *         KICK_OUT("50", "token已被踢下线"),
+ *         ;
+ *
+ *         @Nonnull
+ *         private final String code;
+ *         @Nonnull
+ *         private final String defaultMessage;
+ *
+ *         Type(String code, String defaultMessage) {
+ *             this.code = code;
+ *             this.defaultMessage = defaultMessage;
+ *         }
+ *
+ *         @Override
+ *         @Nonnull
+ *         public String getCode() {
+ *             return code;
+ *         }
+ *
+ *         @Override
+ *         public String getDefaultMessage() {
+ *             return defaultMessage;
+ *         }
+ *
+ *         @Override
+ *         @Nonnull
+ *         public LoginException create() {
+ *             return new LoginException(this, this.defaultMessage);
+ *         }
+ *
+ *         @Override
+ *         @Nonnull
+ *         public LoginException create(String message) {
+ *             return new LoginException(this, message);
+ *         }
+ *
+ *         @Override
+ *         @Nonnull
+ *         public LoginException create(Throwable cause) {
+ *             return new LoginException(this, cause);
+ *         }
+ *
+ *         @Override
+ *         @Nonnull
+ *         public LoginException create(String message, Throwable cause) {
+ *             return new LoginException(this, message, cause);
+ *         }
+ *     }
+ * }
+ * 
+ * + * 使用时,可以使用这种方式创建并抛出异常: + *
+ * throw LoginException.Type.TOKEN_TIMEOUT.create();
+ * 
+ *

+ * + * @author ZhouXY + */ +public interface ExceptionType extends IWithCode { + + String getDefaultMessage(); + + @Nonnull + E create(); + + @Nonnull + E create(String message); + + @Nonnull + E create(Throwable cause); + + @Nonnull + E create(String message, Throwable cause); + +} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/ParsingFailureException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/ParsingFailureException.java index 5f9ae68..0a86545 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/ParsingFailureException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/ParsingFailureException.java @@ -18,13 +18,20 @@ package xyz.zhouxy.plusone.commons.exception; import java.time.format.DateTimeParseException; +import javax.annotation.Nonnull; + +import xyz.zhouxy.plusone.commons.exception.business.RequestParamsException; + /** * 解析失败异常 * *

- * 解析失败的不一定是客户传的参数,也可能是其它来源的数据解析失败 - * 如果表示用户传参造成的解析失败,可使用 RequestParamsException(Throwable cause), - * 将 ParsingFailureException 包装成 {@link RequestParamsException} 再抛出 + * 解析失败的不一定是客户传的参数,也可能是其它来源的数据解析失败。 + * 如果表示用户传参造成的解析失败,可使用 {@link RequestParamsException#RequestParamsException(Throwable)}, + * 将 ParsingFailureException 包装成 {@link RequestParamsException} 再抛出。 + *

+ * throw new RequestParamsException(ParsingFailureException.of(ParsingFailureException.Type.NUMBER_PARSING_FAILURE));
+ * 
*

* * @author ZhouXY @@ -34,13 +41,8 @@ public final class ParsingFailureException extends RuntimeException { private final Type type; - private ParsingFailureException(Type type) { - super(type.getDefaultMsg()); - this.type = type; - } - - private ParsingFailureException(Type type, String msg) { - super(msg); + private ParsingFailureException(Type type, String message) { + super(message); this.type = type; } @@ -49,61 +51,114 @@ public final class ParsingFailureException extends RuntimeException { this.type = type; } - private ParsingFailureException(Type type, String msg, Throwable cause) { - super(msg, cause); + private ParsingFailureException(Type type, String message, Throwable cause) { + super(message, cause); this.type = type; } - public static ParsingFailureException of(Type type) { - return new ParsingFailureException(type); + public ParsingFailureException() { + this(Type.DEFAULT, Type.DEFAULT.getDefaultMessage()); } - public static ParsingFailureException of(Type type, String msg) { - return new ParsingFailureException(type, msg); + public ParsingFailureException(String message) { + this(Type.DEFAULT, message); } - public static ParsingFailureException of(Type type, Throwable e) { - return new ParsingFailureException(type, e); + public ParsingFailureException(Throwable cause) { + this(Type.DEFAULT, cause); } - public static ParsingFailureException of(Type type, String msg, Throwable e) { - return new ParsingFailureException(type, msg, e); + public ParsingFailureException(String message, Throwable cause) { + this(Type.DEFAULT, message, cause); } - public static ParsingFailureException of(DateTimeParseException e) { - return new ParsingFailureException(Type.DATE_TIME_PARSING_FAILURE, e.getMessage(), e); + public static ParsingFailureException of(DateTimeParseException cause) { + if (cause == null) { + return Type.DATE_TIME_PARSING_FAILURE.create(); + } + return Type.DATE_TIME_PARSING_FAILURE.create(cause.getMessage(), cause); } - public static ParsingFailureException of(String msg, DateTimeParseException e) { - return new ParsingFailureException(Type.DATE_TIME_PARSING_FAILURE, msg, e); + public static ParsingFailureException of(String message, DateTimeParseException cause) { + return Type.DATE_TIME_PARSING_FAILURE.create(message, cause); + } + + public static ParsingFailureException of(NumberFormatException cause) { + if (cause == null) { + return Type.NUMBER_PARSING_FAILURE.create(); + } + return Type.NUMBER_PARSING_FAILURE.create(cause.getMessage(), cause); + } + + public static ParsingFailureException of(String message, NumberFormatException cause) { + return Type.NUMBER_PARSING_FAILURE.create(message, cause); } public Type getType() { return type; } - public enum Type { - DEFAULT("4010500", "解析失败"), - NUMBER_PARSING_FAILURE("4010501", "数字转换失败"), - DATE_TIME_PARSING_FAILURE("4010502", "时间解析失败"), - JSON_PARSING_FAILURE("4010503", "JSON 解析失败"), - XML_PARSING_FAILURE("4010504", "XML 解析失败"), + public String getCode() { + return this.type.code; + } + + public static final Type DEFAULT = Type.DEFAULT; + public static final Type NUMBER_PARSING_FAILURE = Type.NUMBER_PARSING_FAILURE; + public static final Type DATE_TIME_PARSING_FAILURE = Type.DATE_TIME_PARSING_FAILURE; + public static final Type JSON_PARSING_FAILURE = Type.JSON_PARSING_FAILURE; + public static final Type XML_PARSING_FAILURE = Type.XML_PARSING_FAILURE; + + public enum Type implements ExceptionType { + DEFAULT("00", "解析失败"), + NUMBER_PARSING_FAILURE("10", "数字转换失败"), + DATE_TIME_PARSING_FAILURE("20", "时间解析失败"), + JSON_PARSING_FAILURE("30", "JSON 解析失败"), + XML_PARSING_FAILURE("40", "XML 解析失败"), ; - final String code; - final String defaultMsg; + @Nonnull + private final String code; + @Nonnull + private final String defaultMessage; - Type(String code, String defaultMsg) { + Type(String code, String defaultMessage) { this.code = code; - this.defaultMsg = defaultMsg; + this.defaultMessage = defaultMessage; } + @Override + @Nonnull public String getCode() { return code; } - public String getDefaultMsg() { - return defaultMsg; + @Override + public String getDefaultMessage() { + return defaultMessage; + } + + @Override + @Nonnull + public ParsingFailureException create() { + return new ParsingFailureException(this, this.defaultMessage); + } + + @Override + @Nonnull + public ParsingFailureException create(String message) { + return new ParsingFailureException(this, message); + } + + @Override + @Nonnull + public ParsingFailureException create(Throwable cause) { + return new ParsingFailureException(this, cause); + } + + @Override + @Nonnull + public ParsingFailureException create(String message, Throwable cause) { + return new ParsingFailureException(this, message, cause); } } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/business/BizException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/business/BizException.java index eee5262..ed4b664 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/business/BizException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/business/BizException.java @@ -37,16 +37,16 @@ public class BizException extends RuntimeException { super(DEFAULT_MSG); } - public BizException(String msg) { - super(msg); + public BizException(String message) { + super(message); } public BizException(Throwable cause) { super(cause); } - public BizException(String msg, Throwable cause) { - super(msg, cause); + public BizException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/business/InvalidInputException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/business/InvalidInputException.java index 8b8495c..aacf7a6 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/business/InvalidInputException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/business/InvalidInputException.java @@ -16,6 +16,10 @@ package xyz.zhouxy.plusone.commons.exception.business; +import javax.annotation.Nonnull; + +import xyz.zhouxy.plusone.commons.exception.ExceptionType; + /** * InvalidInputException * @@ -34,12 +38,12 @@ public final class InvalidInputException extends RequestParamsException { private final Type type; private InvalidInputException(Type type) { - super(type.getDefaultMsg()); + super(type.getDefaultMessage()); this.type = type; } - private InvalidInputException(Type type, String msg) { - super(msg); + private InvalidInputException(Type type, String message) { + super(message); this.type = type; } @@ -48,40 +52,36 @@ public final class InvalidInputException extends RequestParamsException { this.type = type; } - private InvalidInputException(Type type, String msg, Throwable cause) { - super(msg, cause); + private InvalidInputException(Type type, String message, Throwable cause) { + super(message, cause); this.type = type; } - public static InvalidInputException of(Type type) { - return new InvalidInputException(type); + public InvalidInputException() { + this(Type.DEFAULT); } - public static InvalidInputException of(Type type, String msg) { - return new InvalidInputException(type, msg); + public InvalidInputException(String message) { + this(Type.DEFAULT, message); } - public static InvalidInputException of(Type type, Throwable e) { - return new InvalidInputException(type, e); + public InvalidInputException(Throwable cause) { + this(Type.DEFAULT, cause); } - public static InvalidInputException of(Type type, String msg, Throwable e) { - return new InvalidInputException(type, msg, e); - } - - public static InvalidInputException of(Throwable e) { - return new InvalidInputException(Type.DEFAULT, e.getMessage(), e); - } - - public static InvalidInputException of(String msg, Throwable e) { - return new InvalidInputException(Type.DEFAULT, msg, e); + public InvalidInputException(String message, Throwable cause) { + this(Type.DEFAULT, message, cause); } public Type getType() { - return type; + return this.type; } - public enum Type { + public Object getCode() { + return this.type.code; + } + + public enum Type implements ExceptionType { DEFAULT("00", "用户输入内容非法"), CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS("01", "包含非法恶意跳转链接"), CONTAINS_ILLEGAL_WORDS("02", "包含违禁敏感词"), @@ -89,20 +89,49 @@ public final class InvalidInputException extends RequestParamsException { INFRINGE_COPYRIGHT("04", "文件侵犯版权"), ; + @Nonnull final String code; - final String defaultMsg; + @Nonnull + final String defaultMessage; Type(String code, String defaultMsg) { this.code = code; - this.defaultMsg = defaultMsg; + this.defaultMessage = defaultMsg; } + @Override + @Nonnull public String getCode() { return code; } - public String getDefaultMsg() { - return defaultMsg; + @Override + public String getDefaultMessage() { + return defaultMessage; + } + + @Override + @Nonnull + public InvalidInputException create() { + return new InvalidInputException(this); + } + + @Override + @Nonnull + public InvalidInputException create(String message) { + return new InvalidInputException(this, message); + } + + @Override + @Nonnull + public InvalidInputException create(Throwable cause) { + return new InvalidInputException(this, cause); + } + + @Override + @Nonnull + public InvalidInputException create(String message, Throwable cause) { + return new InvalidInputException(this, message, cause); } } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/business/RequestParamsException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/business/RequestParamsException.java index 8f4f383..4d9155a 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/business/RequestParamsException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/business/RequestParamsException.java @@ -34,16 +34,16 @@ public class RequestParamsException extends BizException { super(DEFAULT_MSG); } - public RequestParamsException(String msg) { - super(msg); + public RequestParamsException(String message) { + super(message); } public RequestParamsException(Throwable cause) { super(cause); } - public RequestParamsException(String msg, Throwable cause) { - super(msg, cause); + public RequestParamsException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/system/DataOperationResultException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/system/DataOperationResultException.java index 99db690..bbb49f6 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/system/DataOperationResultException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/system/DataOperationResultException.java @@ -39,15 +39,15 @@ public final class DataOperationResultException extends SysException { super(DEFAULT_MSG); } - public DataOperationResultException(String msg) { - super(msg); + public DataOperationResultException(String message) { + super(message); } public DataOperationResultException(Throwable cause) { super(cause); } - public DataOperationResultException(String msg, Throwable cause) { - super(msg, cause); + public DataOperationResultException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/system/NoAvailableMacFoundException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/system/NoAvailableMacFoundException.java index 7897b46..d7c76f8 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/system/NoAvailableMacFoundException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/system/NoAvailableMacFoundException.java @@ -33,15 +33,15 @@ public class NoAvailableMacFoundException extends SysException { super(); } - public NoAvailableMacFoundException(String msg) { - super(msg); + public NoAvailableMacFoundException(String message) { + super(message); } - public NoAvailableMacFoundException(Throwable e) { - super(e); + public NoAvailableMacFoundException(Throwable cause) { + super(cause); } - public NoAvailableMacFoundException(String msg, Throwable e) { - super(msg, e); + public NoAvailableMacFoundException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/exception/system/SysException.java b/src/main/java/xyz/zhouxy/plusone/commons/exception/system/SysException.java index 877d43a..7480bd6 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/exception/system/SysException.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/exception/system/SysException.java @@ -30,19 +30,19 @@ public class SysException extends RuntimeException { private static final String DEFAULT_MSG = "系统异常"; - protected SysException() { + public SysException() { super(DEFAULT_MSG); } - public SysException(String msg) { - super(msg); + public SysException(String message) { + super(message); } public SysException(Throwable cause) { super(cause); } - public SysException(String msg, Throwable cause) { - super(msg, cause); + public SysException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/function/ThrowingFunction.java b/src/main/java/xyz/zhouxy/plusone/commons/function/ThrowingFunction.java new file mode 100644 index 0000000..08ed960 --- /dev/null +++ b/src/main/java/xyz/zhouxy/plusone/commons/function/ThrowingFunction.java @@ -0,0 +1,29 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.function; + +@FunctionalInterface +public interface ThrowingFunction { + + /** + * Applies this function to the given argument. + * + * @param t the function argument + * @return the function result + */ + R apply(T t) throws E; + +} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/function/package-info.java b/src/main/java/xyz/zhouxy/plusone/commons/function/package-info.java new file mode 100644 index 0000000..2c731f4 --- /dev/null +++ b/src/main/java/xyz/zhouxy/plusone/commons/function/package-info.java @@ -0,0 +1,44 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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. + */ + +/** + * 函数式编程 + * + *

PredicateTools

+ *

+ * {@link PredicateTools} 用于 {@link java.util.function.Predicate} 的相关操作。 + *

+ * + *

Functional interfaces

+ *

+ * 补充一些 JDK 没有,而项目中可能用得上的函数式接口: + *

+ * | Group         | FunctionalInterface  | method                           |
+ * | ------------- | -------------------- | -------------------------------- |
+ * | UnaryOperator | BoolUnaryOperator    | boolean applyAsBool (boolean)    |
+ * | UnaryOperator | CharUnaryOperator    | char applyAsChar(char)           |
+ * | Throwing      | Executable           | void execute() throws E          |
+ * | Throwing      | ThrowingConsumer     | void accept(T) throws E          |
+ * | Throwing      | ThrowingFunction     | R apply(T) throws E              |
+ * | Throwing      | ThrowingPredicate    | boolean test(T) throws E         |
+ * | Throwing      | ThrowingSupplier     | T get() throws E                 |
+ * | Optional      | OptionalSupplier     | Optional<T> get() throws E       |
+ * | Optional      | ToOptionalBiFunction | Optional<R> apply(T,U)           |
+ * | Optional      | ToOptionalFunction   | Optional<R> apply(T)             |
+ * 
+ *

+ */ +package xyz.zhouxy.plusone.commons.function; diff --git a/src/main/java/xyz/zhouxy/plusone/commons/math/Interval.java b/src/main/java/xyz/zhouxy/plusone/commons/math/Interval.java deleted file mode 100644 index aed22e9..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/math/Interval.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.math; - -import java.util.Optional; - -import javax.annotation.Nonnull; - -import com.google.common.base.Preconditions; -import com.google.errorprone.annotations.Immutable; - -import xyz.zhouxy.plusone.commons.util.Numbers; - -@Immutable -public class Interval> { - @Nonnull - private final IntervalType intervalType; - private final T lowerBound; - private final T upperBound; - - public Interval(@Nonnull IntervalType intervalType, T lowerBound, T upperBound) { - Preconditions.checkNotNull(intervalType); - if (intervalType.isLeftClosed()) { - Preconditions.checkArgument(lowerBound != null, - "The lower bound cannot be null, when the interval is left-closed."); - } - if (intervalType.isRightClosed()) { - Preconditions.checkArgument(upperBound != null, - "The upper bound cannot be null, when the interval is right-closed."); - } - if (lowerBound != null && upperBound != null) { - Preconditions.checkArgument(lowerBound.compareTo(upperBound) <= 0, - "The lower bound must less than the upper bound."); - } - this.intervalType = intervalType; - this.lowerBound = lowerBound; - this.upperBound = upperBound; - } - - @Nonnull - public final IntervalType getIntervalType() { - return intervalType; - } - - @Nonnull - public final Optional getLowerBound() { - return Optional.ofNullable(lowerBound); - } - - @Nonnull - public final Optional getUpperBound() { - return Optional.ofNullable(upperBound); - } - - public final boolean isLeftClosed() { - return this.intervalType.isLeftClosed(); - } - - public final boolean isRightClosed() { - return this.intervalType.isRightClosed(); - } - - public final boolean validValue(@Nonnull T value) { - return Numbers.between(value, this.lowerBound, this.upperBound, this.intervalType); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/math/IntervalType.java b/src/main/java/xyz/zhouxy/plusone/commons/math/IntervalType.java deleted file mode 100644 index 1127820..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/math/IntervalType.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.math; - -public enum IntervalType { - /** 开区间。(a,b)={x|a < x < b} */ - OPEN(false, false), - /** 闭区间。[a,b]={x|a ≤ x ≤ b} */ - CLOSED(true, true), - /** 左闭右开区间。[a,b)={x|a ≤ x < b} */ - CLOSED_OPEN(true, false), - /** 左开右闭区间。(a,b]={x|a < x ≤ b} */ - OPEN_CLOSED(false, true); - - private final boolean leftClosed; - private final boolean rightClosed; - - IntervalType(boolean leftClosed, boolean rightClosed) { - this.leftClosed = leftClosed; - this.rightClosed = rightClosed; - } - - public final boolean isLeftClosed() { - return leftClosed; - } - - public final boolean isRightClosed() { - return rightClosed; - } - - public final > Interval buildInterval(T left, T right) { - return new Interval<>(this, left, right); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java b/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java index c6ad7a7..f802468 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumber.java @@ -18,18 +18,35 @@ package xyz.zhouxy.plusone.commons.model; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.time.format.DateTimeParseException; import java.util.Map; import java.util.regex.Matcher; -import java.util.regex.Pattern; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; +import com.google.errorprone.annotations.Immutable; + +import xyz.zhouxy.plusone.commons.annotation.ReaderMethod; +import xyz.zhouxy.plusone.commons.annotation.ValueObject; +import xyz.zhouxy.plusone.commons.constant.PatternConsts; +import xyz.zhouxy.plusone.commons.util.AssertTools; +import xyz.zhouxy.plusone.commons.util.StringTools; /** + * Chinese2ndGenIDCardNumber + * + *

* 中国第二代居民身份证号 + *

+ * + * @author ZhouXY + * @since 1.0 + * @see xyz.zhouxy.plusone.commons.constant.PatternConsts#CHINESE_2ND_ID_CARD_NUMBER */ -public class Chinese2ndGenIDCardNumber extends IDCardNumber { +@ValueObject +@Immutable +public class Chinese2ndGenIDCardNumber + extends ValidatableStringRecord + implements IDCardNumber { /** 省份编码 */ private final String provinceCode; @@ -42,83 +59,101 @@ public class Chinese2ndGenIDCardNumber extends IDCardNumber { /** 出生日期 */ private final LocalDate birthDate; - public static final Pattern PATTERN = Pattern.compile("^(((\\d{2})\\d{2})\\d{2})(\\d{8})\\d{2}(\\d)([\\dXx])$"); private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); - private Chinese2ndGenIDCardNumber(String idNumber) { - super(idNumber, PATTERN, "Invalid ID number"); + private Chinese2ndGenIDCardNumber(String value) { + super(value.toUpperCase(), PatternConsts.CHINESE_2ND_ID_CARD_NUMBER, () -> "二代居民身份证校验失败:" + value); + + final Matcher matcher = getMatcher(); + + final String provinceCodeValue = matcher.group("province"); + AssertTools.checkArgument(Chinese2ndGenIDCardNumber.PROVINCE_CODES.containsKey(provinceCodeValue)); + + final String cityCodeValue = matcher.group("city"); + final String countyCodeValue = matcher.group("county"); + + final Gender genderValue; + final LocalDate birthDateValue; try { - final Matcher matcher = getMatcher(); - this.provinceCode = matcher.group(3); - this.cityCode = matcher.group(2); - this.countyCode = matcher.group(1); + // 出生日期 + final String birthDateStr = matcher.group("birthDate"); + birthDateValue = LocalDate.parse(birthDateStr, DATE_FORMATTER); // 性别 - final String genderStr = matcher.group(5); - final int genderIndex = Integer.parseInt(genderStr); - this.gender = genderIndex % 2 == 0 ? Gender.FEMALE : Gender.MALE; - - // 出生日期 - final String birthDateStr = matcher.group(4); - this.birthDate = LocalDate.parse(birthDateStr, DATE_FORMATTER); + final int genderCode = Integer.parseInt(matcher.group("gender")); + genderValue = genderCode % 2 == 0 ? Gender.FEMALE : Gender.MALE; } - catch (DateTimeParseException e) { + catch (Exception e) { throw new IllegalArgumentException(e); } + + this.provinceCode = provinceCodeValue; + this.cityCode = cityCodeValue; + this.countyCode = countyCodeValue; + this.gender = genderValue; + this.birthDate = birthDateValue; } - public static Chinese2ndGenIDCardNumber of(String idNumber) { - return new Chinese2ndGenIDCardNumber(idNumber); + public static Chinese2ndGenIDCardNumber of(final String value) { + AssertTools.checkArgument(StringTools.isNotBlank(value), "二代居民身份证校验失败:号码为空"); + return new Chinese2ndGenIDCardNumber(value); } + // ================================ + // #region - reader methods + // ================================ + + @ReaderMethod public String getProvinceCode() { return provinceCode; } + @ReaderMethod public String getProvinceName() { return PROVINCE_CODES.get(this.provinceCode); } + @ReaderMethod public String getFullProvinceCode() { return Strings.padEnd(this.provinceCode, 12, '0'); } + @ReaderMethod public String getCityCode() { return cityCode; } + @ReaderMethod public String getFullCityCode() { return Strings.padEnd(this.cityCode, 12, '0'); } + @ReaderMethod public String getCountyCode() { return countyCode; } + @ReaderMethod public String getFullCountyCode() { return Strings.padEnd(this.countyCode, 12, '0'); } + @ReaderMethod @Override public Gender getGender() { return gender; } + @ReaderMethod @Override public LocalDate getBirthDate() { return birthDate; } - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - return super.equals(obj); - } + // ================================ + // #endregion - reader methods + // ================================ /** * 省份代码表 @@ -165,4 +200,14 @@ public class Chinese2ndGenIDCardNumber extends IDCardNumber { .put("91", "国外") .build(); } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/Gender.java b/src/main/java/xyz/zhouxy/plusone/commons/model/Gender.java index 24dbeb9..7922305 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/Gender.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/Gender.java @@ -16,6 +16,7 @@ package xyz.zhouxy.plusone.commons.model; +import xyz.zhouxy.plusone.commons.base.IWithIntCode; import xyz.zhouxy.plusone.commons.util.AssertTools; /** @@ -23,7 +24,7 @@ import xyz.zhouxy.plusone.commons.util.AssertTools; * * @author ZhouXY */ -public enum Gender { +public enum Gender implements IWithIntCode { UNKNOWN(0, "Unknown", "未知"), MALE(1, "Male", "男"), FEMALE(2, "Female", "女"), @@ -60,4 +61,9 @@ public enum Gender { return displayNameZh; } + @Override + public int getCode() { + return getValue(); + } + } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/IDCardNumber.java b/src/main/java/xyz/zhouxy/plusone/commons/model/IDCardNumber.java index f6e3114..3e7c56d 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/IDCardNumber.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/IDCardNumber.java @@ -18,44 +18,60 @@ package xyz.zhouxy.plusone.commons.model; import java.time.LocalDate; import java.time.Period; -import java.util.function.Supplier; -import java.util.regex.Pattern; -import javax.annotation.Nonnull; +import xyz.zhouxy.plusone.commons.util.AssertTools; /** * 身份证号 */ -public abstract class IDCardNumber extends ValidatableStringRecord { +public interface IDCardNumber { - protected IDCardNumber(@Nonnull String idNumber, @Nonnull Pattern pattern) - throws IllegalArgumentException{ - super(idNumber, pattern); - } + static final char DEFAULT_REPLACED_CHAR = '*'; + static final int DEFAULT_DISPLAY_FRONT = 1; + static final int DEFAULT_DISPLAY_END = 2; - protected IDCardNumber(@Nonnull String idNumber, @Nonnull Pattern pattern, - @Nonnull String errorMessage) { - super(idNumber, pattern, errorMessage); - } - - protected IDCardNumber(@Nonnull String idNumber, @Nonnull Pattern pattern, - @Nonnull Supplier errorMessage) { - super(idNumber, pattern, errorMessage); - } + String value(); /** * 根据身份证号判断性别 */ - public abstract Gender getGender(); + Gender getGender(); /** * 获取出生日期 */ - public abstract LocalDate getBirthDate(); + LocalDate getBirthDate(); /** 计算年龄 */ - public final int calculateAge() { + default int getAge() { LocalDate now = LocalDate.now(); return Period.between(getBirthDate(), now).getYears(); } + + // ================================ + // #region - toString + // ================================ + + default String toDesensitizedString() { + return toDesensitizedString(DEFAULT_REPLACED_CHAR, DEFAULT_DISPLAY_FRONT, DEFAULT_DISPLAY_END); + } + + default String toDesensitizedString(int front, int end) { + return toDesensitizedString(DEFAULT_REPLACED_CHAR, front, end); + } + + default String toDesensitizedString(char replacedChar, int front, int end) { + final String value = value(); + AssertTools.checkArgument(front >= 0 && end >= 0); + AssertTools.checkArgument((front + end) <= value.length(), "需要截取的长度不能大于身份证号长度"); + final char[] charArray = value.toCharArray(); + for (int i = front; i < charArray.length - end; i++) { + charArray[i] = replacedChar; + } + return String.valueOf(charArray); + } + + // ================================ + // #endregion - toString + // ================================ } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecord.java b/src/main/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecord.java index fc63894..c679a14 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecord.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecord.java @@ -23,6 +23,7 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; +import xyz.zhouxy.plusone.commons.annotation.ReaderMethod; import xyz.zhouxy.plusone.commons.util.AssertTools; /** @@ -31,8 +32,8 @@ import xyz.zhouxy.plusone.commons.util.AssertTools; * @author ZhouXY * @since 0.1.0 */ -public abstract class ValidatableStringRecord - implements Comparable { +public abstract class ValidatableStringRecord> + implements Comparable { @Nonnull private final String value; @@ -50,8 +51,8 @@ public abstract class ValidatableStringRecord protected ValidatableStringRecord(@Nonnull String value, @Nonnull Pattern pattern, @Nonnull String errorMessage) { - AssertTools.checkArgumentNotNull(value, "The value cannot be null."); - AssertTools.checkArgumentNotNull(pattern, "The pattern cannot be null."); + AssertTools.checkArgument(Objects.nonNull(value), "The value cannot be null."); + AssertTools.checkArgument(Objects.nonNull(pattern), "The pattern cannot be null."); this.matcher = pattern.matcher(value); AssertTools.checkArgument(this.matcher.matches(), errorMessage); this.value = value; @@ -62,18 +63,19 @@ public abstract class ValidatableStringRecord * * @return 字符串(不为空) */ + @ReaderMethod public final String value() { return this.value; } @Override - public int compareTo(ValidatableStringRecord o) { - return this.value.compareTo(o.value); + public int compareTo(T o) { + return this.value.compareTo(o.value()); } @Override public int hashCode() { - return Objects.hash(value); + return Objects.hash(getClass(), value); } @Override @@ -84,8 +86,9 @@ public abstract class ValidatableStringRecord return false; if (getClass() != obj.getClass()) return false; - ValidatableStringRecord other = (ValidatableStringRecord) obj; - return Objects.equals(value, other.value); + @SuppressWarnings("unchecked") + T other = (T) obj; + return Objects.equals(value, other.value()); } @Override diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PageResult.java b/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PageResult.java index 3467182..353d86f 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PageResult.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PageResult.java @@ -16,11 +16,11 @@ package xyz.zhouxy.plusone.commons.model.dto; +import java.util.Collections; import java.util.List; -import com.google.common.base.Preconditions; - import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod; +import xyz.zhouxy.plusone.commons.collection.CollectionTools; /** * 返回分页查询的结果 @@ -37,8 +37,7 @@ public class PageResult { private final List content; private PageResult(List content, long total) { - Preconditions.checkNotNull(content, "Content must not be null."); - this.content = content; + this.content = CollectionTools.nullToEmptyList(content); this.total = total; } @@ -47,6 +46,11 @@ public class PageResult { return new PageResult<>(content, total); } + @StaticFactoryMethod(PageResult.class) + public static PageResult empty() { + return new PageResult<>(Collections.emptyList(), 0L); + } + public long getTotal() { return total; } @@ -57,6 +61,6 @@ public class PageResult { @Override public String toString() { - return "PageDTO [total=" + total + ", content=" + content + "]"; + return "PageResult [total=" + total + ", content=" + content + "]"; } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PagingAndSortingQueryParams.java b/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PagingAndSortingQueryParams.java index 64f8db2..6eb0106 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PagingAndSortingQueryParams.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/dto/PagingAndSortingQueryParams.java @@ -21,12 +21,15 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import xyz.zhouxy.plusone.commons.annotation.Virtual; +import xyz.zhouxy.plusone.commons.collection.CollectionTools; +import xyz.zhouxy.plusone.commons.util.AssertTools; +import xyz.zhouxy.plusone.commons.util.RegexTools; import xyz.zhouxy.plusone.commons.util.StringTools; /** @@ -48,15 +51,15 @@ public class PagingAndSortingQueryParams { private Long pageNum; private List orderBy; - private static final Pattern sortStrPattern = Pattern.compile("^[a-zA-Z]\\w+-(desc|asc|DESC|ASC)$"); + private static final Pattern SORT_STR_PATTERN = Pattern.compile("^[a-zA-Z]\\w+-(desc|asc|DESC|ASC)$"); private final Map sortableProperties; - public PagingAndSortingQueryParams(Map sortableProperties) { - Preconditions.checkArgument(sortableProperties != null && !sortableProperties.isEmpty(), + public PagingAndSortingQueryParams(@Nonnull Map sortableProperties) { + AssertTools.checkArgument(CollectionTools.isNotEmpty(sortableProperties), "Sortable properties can not be empty."); sortableProperties.forEach((k, v) -> - Preconditions.checkArgument(StringTools.isNotBlank(k) && StringTools.isNotBlank(v), + AssertTools.checkArgument(StringTools.isNotBlank(k) && StringTools.isNotBlank(v), "Property name must not be blank.")); this.sortableProperties = ImmutableMap.copyOf(sortableProperties); } @@ -101,12 +104,12 @@ public class PagingAndSortingQueryParams { } private SortableProperty generateSortableProperty(String orderByStr) { - Preconditions.checkArgument(PagingAndSortingQueryParams.sortStrPattern.matcher(orderByStr).matches()); + AssertTools.checkArgument(RegexTools.matches(orderByStr, SORT_STR_PATTERN)); String[] propertyNameAndOrderType = orderByStr.split("-"); - Preconditions.checkArgument(propertyNameAndOrderType.length == 2); + AssertTools.checkArgument(propertyNameAndOrderType.length == 2); String propertyName = propertyNameAndOrderType[0]; - Preconditions.checkArgument(sortableProperties.containsKey(propertyName), + AssertTools.checkArgument(sortableProperties.containsKey(propertyName), "The property name must be in the set of sortable properties."); String columnName = sortableProperties.get(propertyName); String orderType = propertyNameAndOrderType[1]; @@ -123,7 +126,7 @@ public class PagingAndSortingQueryParams { SortableProperty(String propertyName, String columnName, String orderType) { this.propertyName = propertyName; this.columnName = columnName; - Preconditions.checkArgument("ASC".equalsIgnoreCase(orderType) || "DESC".equalsIgnoreCase(orderType)); + AssertTools.checkArgument("ASC".equalsIgnoreCase(orderType) || "DESC".equalsIgnoreCase(orderType)); this.orderType = orderType.toUpperCase(); this.sqlSnippet = this.propertyName + " " + this.orderType; diff --git a/src/main/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponse.java b/src/main/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponse.java index 143fc86..d33d0df 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponse.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponse.java @@ -22,7 +22,7 @@ import java.util.function.Supplier; import javax.annotation.Nullable; -import com.google.common.base.Preconditions; +import xyz.zhouxy.plusone.commons.util.AssertTools; /** * 统一结果,对返回给前端的数据进行封装。 @@ -78,16 +78,16 @@ public abstract class UnifiedResponse { public static UnifiedResponse of(final boolean isSuccess, final Supplier successResult, final Supplier errorResult) { - Preconditions.checkNotNull(successResult, "Success supplier must not be null."); - Preconditions.checkNotNull(errorResult, "Error supplier must not be null."); + AssertTools.checkNotNull(successResult, "Success supplier must not be null."); + AssertTools.checkNotNull(errorResult, "Error supplier must not be null."); return isSuccess ? successResult.get() : errorResult.get(); } public static UnifiedResponse of(final BooleanSupplier isSuccess, final Supplier successResult, final Supplier errorResult) { - Preconditions.checkNotNull(isSuccess, "Conditions for success must not be null."); - Preconditions.checkNotNull(successResult, "Success supplier must not be null."); - Preconditions.checkNotNull(errorResult, "Error supplier must not be null."); + AssertTools.checkNotNull(isSuccess, "Conditions for success must not be null."); + AssertTools.checkNotNull(successResult, "Success supplier must not be null."); + AssertTools.checkNotNull(errorResult, "Error supplier must not be null."); return isSuccess.getAsBoolean() ? successResult.get() : errorResult.get(); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/sql/JdbcSql.java b/src/main/java/xyz/zhouxy/plusone/commons/sql/JdbcSql.java deleted file mode 100644 index d24ed8a..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/sql/JdbcSql.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.sql; - -import java.util.Collection; - -public class JdbcSql extends SQL { - - JdbcSql() { - super(); - } - - public static JdbcSql newSql() { - return new JdbcSql(); - } - - @Override - public JdbcSql getSelf() { - return this; - } - - public static String IN(String col, Collection c) { - return IN(col, c.size()); - } - - public static String IN(String col, T[] c) { - return IN(col, c.length); - } - - private static String IN(String col, int length) { - return col + " IN (" + String.valueOf(buildQuestionsList(length)) + ')'; - } - - public static String NOT_IN(String col, Collection c) { - return NOT_IN(col, c.size()); - } - - public static String NOT_IN(String col, T[] c) { - return NOT_IN(col, c.length); - } - - private static String NOT_IN(String col, int length) { - return col + " NOT IN (" + String.valueOf(buildQuestionsList(length)) + ')'; - } - - private static char[] buildQuestionsList(int times) { - char[] arr = new char[times * 3 - 2]; - int i = 0; - for (int t = 1; t <= times; t++) { - arr[i++] = '?'; - if (t < times) { - arr[i++] = ','; - arr[i++] = ' '; - } - } - return arr; - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/sql/MyBatisSql.java b/src/main/java/xyz/zhouxy/plusone/commons/sql/MyBatisSql.java deleted file mode 100644 index 3592205..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/sql/MyBatisSql.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.sql; - -import com.google.common.annotations.Beta; - -@Beta -public class MyBatisSql extends SQL { - - private final boolean withScript; - - MyBatisSql(boolean withScript) { - super(); - this.withScript = withScript; - } - - public static MyBatisSql newSql() { - return new MyBatisSql(false); - } - - public static MyBatisSql newScriptSql() { - return new MyBatisSql(true); - } - - @Override - public MyBatisSql getSelf() { - return this; - } - - public static String IN(String col, String paramName) { - return " " + col + " IN" + buildForeach(col, paramName); - } - - public static String NOT_IN(String col, String paramName) { - return col + " NOT IN" + buildForeach(col, paramName); - } - - private static String buildForeach(String col, String paramName) { - final String format = "" + - "#{%s}" + - ""; - return String.format(format, col, paramName, col); - } - - @Override - public String toString() { - if (withScript) { - return ""; - } - return super.toString(); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/sql/SQL.java b/src/main/java/xyz/zhouxy/plusone/commons/sql/SQL.java deleted file mode 100644 index b0ef318..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/sql/SQL.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.sql; - -import org.apache.ibatis.jdbc.AbstractSQL; - -import com.google.common.annotations.Beta; - -/** - * @author ZhouXY - */ -@Beta -public abstract class SQL extends AbstractSQL { - - public static JdbcSql newJdbcSql() { - return new JdbcSql(); - } - - public static MyBatisSql newMyBatisSql(boolean withScript) { - return new MyBatisSql(withScript); - } - - public T WHERE(boolean condition, String sqlCondition) { - if (condition) { - return WHERE(sqlCondition); - } - return getSelf(); - } - - public T WHERE(boolean condition, String ifSqlCondition, String elseSqlCondition) { - return WHERE(condition ? ifSqlCondition : elseSqlCondition); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/time/Quarter.java b/src/main/java/xyz/zhouxy/plusone/commons/time/Quarter.java index db2a5f6..279ca23 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/time/Quarter.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/time/Quarter.java @@ -16,20 +16,23 @@ package xyz.zhouxy.plusone.commons.time; +import java.time.DateTimeException; import java.time.Month; import java.time.MonthDay; +import java.time.temporal.ChronoField; -import com.google.common.base.Preconditions; +import com.google.common.collect.Range; import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod; -import xyz.zhouxy.plusone.commons.util.Numbers; +import xyz.zhouxy.plusone.commons.base.IWithIntCode; +import xyz.zhouxy.plusone.commons.util.AssertTools; /** * 季度 * * @author ZhouXY */ -public enum Quarter { +public enum Quarter implements IWithIntCode { /** 第一季度 */ Q1(1), /** 第二季度 */ @@ -43,11 +46,7 @@ public enum Quarter { /** 季度值 (1/2/3/4) */ private final int value; - /** 季度开始月份 */ - private final int firstMonth; - - /** 季度结束月份 */ - private final int lastMonth; + private final Range monthRange; /** 常量值 */ private static final Quarter[] ENUMS = Quarter.values(); @@ -58,8 +57,10 @@ public enum Quarter { Quarter(int value) { this.value = value; - this.lastMonth = value * 3; - this.firstMonth = this.lastMonth - 2; + final int lastMonth = value * 3; + final int firstMonth = lastMonth - 2; + + this.monthRange = Range.closed(firstMonth, lastMonth); } // StaticFactoryMethods @@ -73,7 +74,7 @@ public enum Quarter { */ @StaticFactoryMethod(Quarter.class) public static Quarter fromMonth(int monthValue) { - Preconditions.checkArgument(Numbers.between(monthValue, 1, 13), "Invalid value for MonthOfYear: " + monthValue); + ChronoField.MONTH_OF_YEAR.checkValidValue(monthValue); return of(computeQuarterValueInternal(monthValue)); } @@ -109,26 +110,23 @@ public enum Quarter { */ @StaticFactoryMethod(Quarter.class) public static Quarter of(int value) { - if (value < 1 || value > 4) { - throw new IllegalArgumentException("Invalid value for Quarter: " + value); - } - return ENUMS[value - 1]; + return ENUMS[checkValidIntValue(value) - 1]; } // StaticFactoryMethods end - // computs + // computes - public Quarter plus(long quarters) { // TODO 单元测试 + public Quarter plus(long quarters) { final int amount = (int) ((quarters % 4) + 4); return ENUMS[(ordinal() + amount) % 4]; } - public Quarter minus(long quarters) { // TODO 单元测试 + public Quarter minus(long quarters) { return plus(-(quarters % 4)); } - // computs end + // computes end // Getters @@ -136,24 +134,29 @@ public enum Quarter { return value; } + @Override + public int getCode() { + return getValue(); + } + public Month firstMonth() { - return Month.of(firstMonth); + return Month.of(firstMonthValue()); } public int firstMonthValue() { - return firstMonth; + return this.monthRange.lowerEndpoint(); } public Month lastMonth() { - return Month.of(lastMonth); + return Month.of(lastMonthValue()); } public int lastMonthValue() { - return lastMonth; + return this.monthRange.upperEndpoint(); } public MonthDay firstMonthDay() { - return MonthDay.of(this.firstMonth, 1); + return MonthDay.of(firstMonth(), 1); } public MonthDay lastMonthDay() { @@ -163,11 +166,17 @@ public enum Quarter { } public int firstDayOfYear(boolean leapYear) { - return Month.of(this.firstMonth).firstDayOfYear(leapYear); + return firstMonth().firstDayOfYear(leapYear); } // Getters end + public static int checkValidIntValue(int value) { + AssertTools.checkCondition(value >= 1 && value <= 4, + () -> new DateTimeException("Invalid value for Quarter: " + value)); + return value; + } + // Internal /** diff --git a/src/main/java/xyz/zhouxy/plusone/commons/time/YearQuarter.java b/src/main/java/xyz/zhouxy/plusone/commons/time/YearQuarter.java index 2925d99..947b065 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/time/YearQuarter.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/time/YearQuarter.java @@ -22,16 +22,17 @@ import java.io.Serializable; import java.time.LocalDate; import java.time.Month; import java.time.YearMonth; +import java.time.temporal.ChronoField; import java.util.Calendar; import java.util.Date; import java.util.Objects; import javax.annotation.Nonnull; -import com.google.common.base.Preconditions; import com.google.errorprone.annotations.Immutable; import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod; +import xyz.zhouxy.plusone.commons.util.AssertTools; /** * 表示年份与季度 @@ -52,14 +53,13 @@ public final class YearQuarter implements Comparable, Serializable private final LocalDate lastDate; private YearQuarter(int year, @Nonnull Quarter quarter) { - Preconditions.checkNotNull(quarter, "Quarter can not be null."); this.year = year; this.quarter = quarter; this.firstDate = quarter.firstMonthDay().atYear(year); this.lastDate = quarter.lastMonthDay().atYear(year); } - // #region - StaticFactoryMethod + // #region - StaticFactory /** * 根据指定年份与季度,创建 {@link YearQuarter} 实例 @@ -70,7 +70,7 @@ public final class YearQuarter implements Comparable, Serializable */ @StaticFactoryMethod(YearQuarter.class) public static YearQuarter of(int year, int quarter) { - return of(year, Quarter.of(quarter)); + return new YearQuarter(YEAR.checkValidIntValue(year), Quarter.of(quarter)); } /** @@ -81,8 +81,8 @@ public final class YearQuarter implements Comparable, Serializable * @return {@link YearQuarter} 实例 */ @StaticFactoryMethod(YearQuarter.class) - public static YearQuarter of(int year, @Nonnull Quarter quarter) { - return new YearQuarter(year, quarter); + public static YearQuarter of(int year, Quarter quarter) { + return new YearQuarter(YEAR.checkValidIntValue(year), Objects.requireNonNull(quarter)); } /** @@ -92,8 +92,9 @@ public final class YearQuarter implements Comparable, Serializable * @return {@link YearQuarter} 实例 */ @StaticFactoryMethod(YearQuarter.class) - public static YearQuarter of(@Nonnull LocalDate date) { - return of(date.getYear(), Quarter.fromMonth(date.getMonth())); + public static YearQuarter of(LocalDate date) { + AssertTools.checkNotNull(date); + return new YearQuarter(date.getYear(), Quarter.fromMonth(date.getMonth())); } /** @@ -103,12 +104,13 @@ public final class YearQuarter implements Comparable, Serializable * @return {@link YearQuarter} 实例 */ @StaticFactoryMethod(YearQuarter.class) - public static YearQuarter of(@Nonnull Date date) { + public static YearQuarter of(Date date) { + AssertTools.checkNotNull(date); @SuppressWarnings("deprecation") - final int year = date.getYear() + 1900; + final int yearValue = YEAR.checkValidIntValue(date.getYear() + 1900L); @SuppressWarnings("deprecation") - final int month = date.getMonth() + 1; - return of(year, Quarter.fromMonth(month)); + final int monthValue = date.getMonth() + 1; + return new YearQuarter(yearValue, Quarter.fromMonth(monthValue)); } /** @@ -119,7 +121,10 @@ public final class YearQuarter implements Comparable, Serializable */ @StaticFactoryMethod(YearQuarter.class) public static YearQuarter of(Calendar date) { - return of(date.get(Calendar.YEAR), Quarter.fromMonth(date.get(Calendar.MONTH) + 1)); + AssertTools.checkNotNull(date); + final int yearValue = ChronoField.YEAR.checkValidIntValue(date.get(Calendar.YEAR)); + final int monthValue = date.get(Calendar.MONTH) + 1; + return new YearQuarter(yearValue, Quarter.fromMonth(monthValue)); } /** @@ -130,19 +135,29 @@ public final class YearQuarter implements Comparable, Serializable */ @StaticFactoryMethod(YearQuarter.class) public static YearQuarter of(YearMonth yearMonth) { + AssertTools.checkNotNull(yearMonth); return of(yearMonth.getYear(), Quarter.fromMonth(yearMonth.getMonth())); } + @StaticFactoryMethod(YearQuarter.class) + public static YearQuarter now() { + return of(LocalDate.now()); + } + // #endregion // #region - Getters public int getYear() { - return year; + return this.year; } public Quarter getQuarter() { - return quarter; + return this.quarter; + } + + public int getQuarterValue() { + return this.quarter.getValue(); } public YearMonth firstYearMonth() { @@ -181,33 +196,49 @@ public final class YearQuarter implements Comparable, Serializable // #region - computes - public YearQuarter plusQuarters(long quartersToAdd) { // TODO 单元测试 - if (quartersToAdd == 0) { + public YearQuarter plusQuarters(long quartersToAdd) { + if (quartersToAdd == 0L) { return this; } long quarterCount = this.year * 4L + (this.quarter.getValue() - 1); long calcQuarters = quarterCount + quartersToAdd; // safe overflow int newYear = YEAR.checkValidIntValue(Math.floorDiv(calcQuarters, 4)); int newQuarter = (int) Math.floorMod(calcQuarters, 4) + 1; - return of(newYear, Quarter.of(newQuarter)); + return new YearQuarter(newYear, Quarter.of(newQuarter)); } - public YearQuarter minusQuarters(long quartersToAdd) { // TODO 单元测试 + public YearQuarter minusQuarters(long quartersToAdd) { return plusQuarters(-quartersToAdd); } - public YearQuarter plusYears(long yearsToAdd) { // TODO 单元测试 - if (yearsToAdd == 0) { + public YearQuarter nextQuarter() { + return plusQuarters(1L); + } + + public YearQuarter lastQuarter() { + return minusQuarters(1L); + } + + public YearQuarter plusYears(long yearsToAdd) { + if (yearsToAdd == 0L) { return this; } int newYear = YEAR.checkValidIntValue(this.year + yearsToAdd); // safe overflow - return of(newYear, this.quarter); + return new YearQuarter(newYear, this.quarter); } public YearQuarter minusYears(long yearsToAdd) { return plusYears(-yearsToAdd); } + public YearQuarter nextYear() { + return plusYears(1L); + } + + public YearQuarter lastYear() { + return minusYears(1L); + } + // #endregion // #region - hashCode & equals @@ -231,7 +262,7 @@ public final class YearQuarter implements Comparable, Serializable // #endregion - // #region - compareTo + // #region - compare @Override public int compareTo(YearQuarter other) { @@ -250,6 +281,14 @@ public final class YearQuarter implements Comparable, Serializable return this.compareTo(other) > 0; } + public static YearQuarter min(YearQuarter yearQuarter1, YearQuarter yearQuarter2) { + return yearQuarter1.compareTo(yearQuarter2) <= 0 ? yearQuarter1 : yearQuarter2; + } + + public static YearQuarter max(YearQuarter yearQuarter1, YearQuarter yearQuarter2) { + return yearQuarter1.compareTo(yearQuarter2) >= 0 ? yearQuarter1 : yearQuarter2; + } + // #endregion // #region - toString diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/ArrayTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/ArrayTools.java index 1fd381d..8ad67ee 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/ArrayTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/ArrayTools.java @@ -16,26 +16,45 @@ package xyz.zhouxy.plusone.commons.util; +import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nonnull; import javax.annotation.Nullable; +/** + * ArrayTools + * + *

+ * 数组工具类 + *

+ * + * @author ZhouXY + * @since 0.1.0 + */ public class ArrayTools { // #region - empty arrays public static final char[] EMPTY_CHAR_ARRAY = {}; - public static final int[] EMPTY_INTEGER_ARRAY = {}; + public static final byte[] EMPTY_BYTE_ARRAY = {}; + public static final short[] EMPTY_SHORT_ARRAY = {}; + public static final int[] EMPTY_INT_ARRAY = {}; public static final long[] EMPTY_LONG_ARRAY = {}; public static final float[] EMPTY_FLOAT_ARRAY = {}; public static final double[] EMPTY_DOUBLE_ARRAY = {}; // #endregion + public static final int NOT_FOUND_INDEX = -1; + // #region - isNullOrEmpty // isNullOrEmpty @@ -51,6 +70,61 @@ public class ArrayTools { return arr == null || arr.length == 0; } + // isNullOrEmpty - char + /** + * 检查给定数组是否为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNullOrEmpty(@Nullable char[] arr) { + return arr == null || arr.length == 0; + } + + // isNullOrEmpty - byte + /** + * 检查给定数组是否为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNullOrEmpty(@Nullable byte[] arr) { + return arr == null || arr.length == 0; + } + + // isNullOrEmpty - short + /** + * 检查给定数组是否为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNullOrEmpty(@Nullable short[] arr) { + return arr == null || arr.length == 0; + } + + // isNullOrEmpty - int + /** + * 检查给定数组是否为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNullOrEmpty(@Nullable int[] arr) { + return arr == null || arr.length == 0; + } + + // isNullOrEmpty - long + /** + * 检查给定数组是否为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNullOrEmpty(@Nullable long[] arr) { + return arr == null || arr.length == 0; + } + // isNullOrEmpty - float /** * 检查给定数组是否为空 @@ -73,39 +147,6 @@ public class ArrayTools { return arr == null || arr.length == 0; } - // isNullOrEmpty - byte - /** - * 检查给定数组是否为空 - * - * @param arr 待检查的数组,可以为 {@code null} - * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} - */ - public static boolean isNullOrEmpty(@Nullable byte[] arr) { - return arr == null || arr.length == 0; - } - - // isNullOrEmpty - long - /** - * 检查给定数组是否为空 - * - * @param arr 待检查的数组,可以为 {@code null} - * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} - */ - public static boolean isNullOrEmpty(@Nullable long[] arr) { - return arr == null || arr.length == 0; - } - - // isNullOrEmpty - int - /** - * 检查给定数组是否为空 - * - * @param arr 待检查的数组,可以为 {@code null} - * @return 如果数组为 {@code null} 或长度为 0,则返回 {@code true};否则返回 {@code false} - */ - public static boolean isNullOrEmpty(@Nullable int[] arr) { - return arr == null || arr.length == 0; - } - // #endregion // #region - isNotEmpty @@ -122,6 +163,61 @@ public class ArrayTools { return arr != null && arr.length > 0; } + // isNotEmpty - char + /** + * 检查给定数组是否不为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNotEmpty(@Nullable char[] arr) { + return arr != null && arr.length > 0; + } + + // isNotEmpty - byte + /** + * 检查给定数组是否不为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNotEmpty(@Nullable byte[] arr) { + return arr != null && arr.length > 0; + } + + // isNotEmpty - short + /** + * 检查给定数组是否不为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNotEmpty(@Nullable short[] arr) { + return arr != null && arr.length > 0; + } + + // isNotEmpty - int + /** + * 检查给定数组是否不为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNotEmpty(@Nullable int[] arr) { + return arr != null && arr.length > 0; + } + + // isNotEmpty - long + /** + * 检查给定数组是否不为空 + * + * @param arr 待检查的数组,可以为 {@code null} + * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} + */ + public static boolean isNotEmpty(@Nullable long[] arr) { + return arr != null && arr.length > 0; + } + // isNotEmpty - float /** * 检查给定数组是否不为空 @@ -144,39 +240,6 @@ public class ArrayTools { return arr != null && arr.length > 0; } - // isNotEmpty - byte - /** - * 检查给定数组是否不为空 - * - * @param arr 待检查的数组,可以为 {@code null} - * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} - */ - public static boolean isNotEmpty(@Nullable byte[] arr) { - return arr != null && arr.length > 0; - } - - // isNotEmpty - long - /** - * 检查给定数组是否不为空 - * - * @param arr 待检查的数组,可以为 {@code null} - * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} - */ - public static boolean isNotEmpty(@Nullable long[] arr) { - return arr != null && arr.length > 0; - } - - // isNotEmpty - int - /** - * 检查给定数组是否不为空 - * - * @param arr 待检查的数组,可以为 {@code null} - * @return 如果数组不为 {@code null} 且长度大于 0,则返回 {@code true};否则返回 {@code false} - */ - public static boolean isNotEmpty(@Nullable int[] arr) { - return arr != null && arr.length > 0; - } - // #endregion // #region - isAllElementsNotNull @@ -198,12 +261,7 @@ public class ArrayTools { */ public static boolean isAllElementsNotNull(@Nonnull final T[] arr) { AssertTools.checkArgument(arr != null, "The array cannot be null."); - for (T element : arr) { - if (element == null) { - return false; - } - } - return true; + return Arrays.stream(arr).allMatch(Objects::nonNull); } // #endregion @@ -216,14 +274,18 @@ public class ArrayTools { * @param arrays 数组集合,可以为 {@code null} * @return 拼接后的数组 */ - public static float[] concatFloatArray(@Nullable Collection arrays) { + public static char[] concatCharArray(@Nullable final Collection arrays) { if (arrays == null || arrays.isEmpty()) { - return new float[0]; + return new char[0]; } - final int length = arrays.stream().mapToInt(a -> a.length).sum(); - final float[] result = new float[length]; + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); + final char[] result = new char[length]; int i = 0; - for (float[] arr : arrays) { + for (char[] arr : arraysToConcat) { System.arraycopy(arr, 0, result, i, arr.length); i += arr.length; } @@ -236,34 +298,18 @@ public class ArrayTools { * @param arrays 数组集合,可以为 {@code null} * @return 拼接后的数组 */ - public static double[] concatDoubleArray(@Nullable Collection arrays) { - if (arrays == null || arrays.isEmpty()) { - return new double[0]; - } - final int length = arrays.stream().mapToInt(a -> a.length).sum(); - final double[] result = new double[length]; - int i = 0; - for (double[] arr : arrays) { - System.arraycopy(arr, 0, result, i, arr.length); - i += arr.length; - } - return result; - } - - /** - * 拼接多个数组 - * - * @param arrays 数组集合,可以为 {@code null} - * @return 拼接后的数组 - */ - public static byte[] concatByteArray(@Nullable Collection arrays) { + public static byte[] concatByteArray(@Nullable final Collection arrays) { if (arrays == null || arrays.isEmpty()) { return new byte[0]; } - final int length = arrays.stream().mapToInt(a -> a.length).sum(); + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); final byte[] result = new byte[length]; int i = 0; - for (byte[] arr : arrays) { + for (byte[] arr : arraysToConcat) { System.arraycopy(arr, 0, result, i, arr.length); i += arr.length; } @@ -276,14 +322,18 @@ public class ArrayTools { * @param arrays 数组集合,可以为 {@code null} * @return 拼接后的数组 */ - public static long[] concatLongArray(@Nullable Collection arrays) { + public static short[] concatShortArray(@Nullable final Collection arrays) { if (arrays == null || arrays.isEmpty()) { - return new long[0]; + return new short[0]; } - final int length = arrays.stream().mapToInt(a -> a.length).sum(); - final long[] result = new long[length]; + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); + final short[] result = new short[length]; int i = 0; - for (long[] arr : arrays) { + for (short[] arr : arraysToConcat) { System.arraycopy(arr, 0, result, i, arr.length); i += arr.length; } @@ -296,14 +346,90 @@ public class ArrayTools { * @param arrays 数组集合,可以为 {@code null} * @return 拼接后的数组 */ - public static int[] concatIntArray(@Nullable Collection arrays) { + public static int[] concatIntArray(@Nullable final Collection arrays) { if (arrays == null || arrays.isEmpty()) { return new int[0]; } - final int length = arrays.stream().mapToInt(a -> a.length).sum(); + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); final int[] result = new int[length]; int i = 0; - for (int[] arr : arrays) { + for (int[] arr : arraysToConcat) { + System.arraycopy(arr, 0, result, i, arr.length); + i += arr.length; + } + return result; + } + + /** + * 拼接多个数组 + * + * @param arrays 数组集合,可以为 {@code null} + * @return 拼接后的数组 + */ + public static long[] concatLongArray(@Nullable final Collection arrays) { + if (arrays == null || arrays.isEmpty()) { + return new long[0]; + } + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); + final long[] result = new long[length]; + int i = 0; + for (long[] arr : arraysToConcat) { + System.arraycopy(arr, 0, result, i, arr.length); + i += arr.length; + } + return result; + } + + /** + * 拼接多个数组 + * + * @param arrays 数组集合,可以为 {@code null} + * @return 拼接后的数组 + */ + public static float[] concatFloatArray(@Nullable final Collection arrays) { + if (arrays == null || arrays.isEmpty()) { + return new float[0]; + } + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); + final float[] result = new float[length]; + int i = 0; + for (float[] arr : arraysToConcat) { + System.arraycopy(arr, 0, result, i, arr.length); + i += arr.length; + } + return result; + } + + /** + * 拼接多个数组 + * + * @param arrays 数组集合,可以为 {@code null} + * @return 拼接后的数组 + */ + public static double[] concatDoubleArray(@Nullable final Collection arrays) { + if (arrays == null || arrays.isEmpty()) { + return new double[0]; + } + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); + final double[] result = new double[length]; + int i = 0; + for (double[] arr : arraysToConcat) { System.arraycopy(arr, 0, result, i, arr.length); i += arr.length; } @@ -317,17 +443,21 @@ public class ArrayTools { * @param 泛型参数,表示数组的元素类型 * @return 返回连接后的列表,如果输入的集合为空或包含空数组,则返回空列表 */ - public static List concatToList(@Nullable Collection arrays) { + public static List concatToList(@Nullable final Collection arrays) { // 如果输入的集合是否为空,则直接返回一个空列表 if (arrays == null || arrays.isEmpty()) { return Collections.emptyList(); } + final Collection arraysToConcat = arrays + .stream() + .filter(Objects::nonNull) + .collect(Collectors.toList()); // 计算所有数组的总长度,用于初始化列表的容量 - final int length = arrays.stream().mapToInt(a -> a.length).sum(); + final int length = arraysToConcat.stream().mapToInt(a -> a.length).sum(); final List result = new ArrayList<>(length); - for (T[] arr : arrays) { + for (T[] arr : arraysToConcat) { Collections.addAll(result, arr); } @@ -337,11 +467,162 @@ public class ArrayTools { // #endregion + // #region - repeat + + // repeat - char + + public static char[] repeat(char[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static char[] repeat(char[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_CHAR_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final char[] result = new char[length]; + fill(result, 0, length, arr); + return result; + } + + // repeat - byte + + public static byte[] repeat(byte[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static byte[] repeat(byte[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_BYTE_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final byte[] result = new byte[length]; + fill(result, 0, length, arr); + return result; + } + + // repeat - short + + public static short[] repeat(short[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static short[] repeat(short[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_SHORT_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final short[] result = new short[length]; + fill(result, 0, length, arr); + return result; + } + + // repeat - int + + public static int[] repeat(int[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static int[] repeat(int[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_INT_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final int[] result = new int[length]; + fill(result, 0, length, arr); + return result; + } + + // repeat - long + + public static long[] repeat(long[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static long[] repeat(long[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_LONG_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final long[] result = new long[length]; + fill(result, 0, length, arr); + return result; + } + + // repeat - float + + public static float[] repeat(float[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static float[] repeat(float[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_FLOAT_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final float[] result = new float[length]; + fill(result, 0, length, arr); + return result; + } + + // repeat - double + + public static double[] repeat(double[] arr, int times) { + return repeat(arr, times, Integer.MAX_VALUE); + } + + public static double[] repeat(double[] arr, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(arr)); + AssertTools.checkArgument(times >= 0, + "The number of times must be greater than or equal to zero"); + AssertTools.checkArgument(maxLength >= 0, + "The max length must be greater than or equal to zero"); + if (times == 0) { + return EMPTY_DOUBLE_ARRAY; + } + final int length = Integer.min(arr.length * times, maxLength); + final double[] result = new double[length]; + fill(result, 0, length, arr); + return result; + } + + // #endregion + // #region - fill // fill - char - public static void fill(char[] a, char... values) { + public static void fill(char[] a, char[] values) { fill(a, 0, a.length, values); } @@ -349,9 +630,96 @@ public class ArrayTools { fill(a, 0, a.length, values != null ? values.toCharArray() : EMPTY_CHAR_ARRAY); } - public static void fill(char[] a, int fromIndex, int toIndex, char... values) { - AssertTools.checkArgumentNotNull(a); - if (values.length == 0) { + public static void fill(char[] a, int fromIndex, int toIndex, char[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { + return; + } + final int start = Integer.max(fromIndex, 0); + final int end = Integer.min(toIndex, a.length); + if (start >= end) { + return; + } + for (int i = start; i < end; i += values.length) { + for (int j = 0; j < values.length; j++) { + final int k = (i + j); + if (k < end) { + a[k] = values[j]; + } + else { + break; + } + } + } + } + + // fill - byte + + public static void fill(byte[] a, byte[] values) { + fill(a, 0, a.length, values); + } + + public static void fill(byte[] a, int fromIndex, int toIndex, byte[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { + return; + } + final int start = Integer.max(fromIndex, 0); + final int end = Integer.min(toIndex, a.length); + if (start >= end) { + return; + } + for (int i = start; i < end; i += values.length) { + for (int j = 0; j < values.length; j++) { + final int k = (i + j); + if (k < end) { + a[k] = values[j]; + } + else { + break; + } + } + } + } + + // fill - short + + public static void fill(short[] a, short[] values) { + fill(a, 0, a.length, values); + } + + public static void fill(short[] a, int fromIndex, int toIndex, short[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { + return; + } + final int start = Integer.max(fromIndex, 0); + final int end = Integer.min(toIndex, a.length); + if (start >= end) { + return; + } + for (int i = start; i < end; i += values.length) { + for (int j = 0; j < values.length; j++) { + final int k = (i + j); + if (k < end) { + a[k] = values[j]; + } + else { + break; + } + } + } + } + + // fill - int + + public static void fill(int[] a, int[] values) { + fill(a, 0, a.length, values); + } + + public static void fill(int[] a, int fromIndex, int toIndex, int[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { return; } final int start = Integer.max(fromIndex, 0); @@ -374,13 +742,13 @@ public class ArrayTools { // fill - long - public static void fill(long[] a, long... values) { + public static void fill(long[] a, long[] values) { fill(a, 0, a.length, values); } - public static void fill(long[] a, int fromIndex, int toIndex, long... values) { - AssertTools.checkArgumentNotNull(a); - if (values.length == 0) { + public static void fill(long[] a, int fromIndex, int toIndex, long[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { return; } final int start = Integer.max(fromIndex, 0); @@ -403,13 +771,13 @@ public class ArrayTools { // fill - float - public static void fill(float[] a, float... values) { + public static void fill(float[] a, float[] values) { fill(a, 0, a.length, values); } - public static void fill(float[] a, int fromIndex, int toIndex, float... values) { - AssertTools.checkArgumentNotNull(a); - if (values.length == 0) { + public static void fill(float[] a, int fromIndex, int toIndex, float[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { return; } final int start = Integer.max(fromIndex, 0); @@ -432,13 +800,13 @@ public class ArrayTools { // fill - double - public static void fill(double[] a, double... values) { + public static void fill(double[] a, double[] values) { fill(a, 0, a.length, values); } - public static void fill(double[] a, int fromIndex, int toIndex, double... values) { - AssertTools.checkArgumentNotNull(a); - if (values.length == 0) { + public static void fill(double[] a, int fromIndex, int toIndex, double[] values) { + AssertTools.checkArgument(Objects.nonNull(a)); + if (values == null || values.length == 0) { return; } final int start = Integer.max(fromIndex, 0); @@ -470,7 +838,7 @@ public class ArrayTools { } private static void fillInternal(@Nonnull T[] a, int fromIndex, int toIndex, @Nullable T[] values) { - AssertTools.checkArgumentNotNull(a); + AssertTools.checkArgument(Objects.nonNull(a)); if (values == null || values.length == 0) { return; } @@ -494,6 +862,256 @@ public class ArrayTools { // #endregion + // #region - indexOf + + public static int indexOfWithPredicate(T[] arr, Predicate predicate) { + AssertTools.checkNotNull(predicate); + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (predicate.test(arr[i])) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(T[] arr, T obj) { + return indexOfWithPredicate(arr, item -> Objects.equals(item, obj)); + } + + public static int indexOf(char[] arr, char value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(byte[] arr, byte value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(short[] arr, short value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(int[] arr, int value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(long[] arr, long value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(float[] arr, float value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int indexOf(double[] arr, double value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = 0; i < arr.length; i++) { + if (arr[i] == value) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + // #endregion + + // #region - lastIndexOf + + public static int lastIndexOfWithPredicate(T[] arr, @Nonnull Predicate predicate) { + AssertTools.checkNotNull(predicate); + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (predicate.test(arr[i])) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(T[] arr, T obj) { + return lastIndexOfWithPredicate(arr, item -> Objects.equals(item, obj)); + } + + public static int lastIndexOf(char[] arr, char value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(byte[] arr, byte value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(short[] arr, short value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(int[] arr, int value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(long[] arr, long value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(float[] arr, float value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + public static int lastIndexOf(double[] arr, double value) { + if (isNullOrEmpty(arr)) { + return NOT_FOUND_INDEX; + } + for (int i = arr.length - 1; i >= 0; i--) { + if (value == arr[i]) { + return i; + } + } + return NOT_FOUND_INDEX; + } + + // #endregion + + // #region - contains + + public static boolean contains(T[] arr, T obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(char[] arr, char obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(byte[] arr, byte obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(short[] arr, short obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(int[] arr, int obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(long[] arr, long obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(float[] arr, float obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean contains(double[] arr, double obj) { + return indexOf(arr, obj) > NOT_FOUND_INDEX; + } + + public static boolean containsValue(BigDecimal[] arr, BigDecimal obj) { + return indexOfWithPredicate(arr, item -> BigDecimals.equalsValue(item, obj)) > NOT_FOUND_INDEX; + } + + // #endregion + // #region - private constructor private ArrayTools() { diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/AssertTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/AssertTools.java index 8212eb7..cf31308 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/AssertTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/AssertTools.java @@ -16,10 +16,15 @@ package xyz.zhouxy.plusone.commons.util; +import java.util.Objects; +import java.util.Optional; import java.util.function.Supplier; import javax.annotation.Nonnull; +import xyz.zhouxy.plusone.commons.exception.DataNotExistsException; +import xyz.zhouxy.plusone.commons.exception.system.DataOperationResultException; + /** * 断言工具 * @@ -30,91 +35,249 @@ import javax.annotation.Nonnull; *
  * AssertTools.checkArgument(StringUtils.hasText(str), "The argument cannot be blank.");
  * AssertTools.checkState(ArrayUtils.isNotEmpty(result), "The result cannot be empty.");
- * AssertTools.checkCondition(!CollectionUtils.isEmpty(roles), () -> new InvalidInputException("The roles cannot be empty."));
- * AssertTools.checkCondition(RegexTools.matches(email, PatternConsts.EMAIL), "must be a well-formed email address");
+ * AssertTools.checkCondition(!CollectionUtils.isEmpty(roles),
+ *     () -> new InvalidInputException("The roles cannot be empty."));
+ * AssertTools.checkCondition(RegexTools.matches(email, PatternConsts.EMAIL),
+ *     "must be a well-formed email address");
  * 
* * @author ZhouXY */ public class AssertTools { - // #region - checkArgument - - public static void checkArgumentNotNull(T argument) { - checkCondition(argument != null, () -> new IllegalArgumentException("The argument cannot be null.")); - } - - public static void checkArgumentNotNull(T argument, String errMsg) { - checkCondition(argument != null, () -> new IllegalArgumentException(errMsg)); - } - - public static void checkArgumentNotNull(T argument, Supplier messageSupplier) { - checkCondition(argument != null, () -> new IllegalArgumentException(messageSupplier.get())); - } - - public static void checkArgumentNotNull(T argument, String format, Object... args) { - checkCondition(argument != null, () -> new IllegalArgumentException(String.format(format, args))); - } + // ================================ + // #region - Argument + // ================================ + /** Throw {@link IllegalArgumentException} if the {@code condition} is false. */ public static void checkArgument(boolean condition) { checkCondition(condition, IllegalArgumentException::new); } + /** Throw {@link IllegalArgumentException} if the {@code condition} is false. */ public static void checkArgument(boolean condition, String errMsg) { checkCondition(condition, () -> new IllegalArgumentException(errMsg)); } - public static void checkArgument(boolean condition, Supplier messageSupplier) { + /** Throw {@link IllegalArgumentException} if the {@code condition} is false. */ + public static void checkArgument(boolean condition, @Nonnull Supplier messageSupplier) { checkCondition(condition, () -> new IllegalArgumentException(messageSupplier.get())); } + /** Throw {@link IllegalArgumentException} if the {@code condition} is false. */ public static void checkArgument(boolean condition, String format, Object... args) { checkCondition(condition, () -> new IllegalArgumentException(String.format(format, args))); } - // #endregion + // ================================ + // #endregion - Argument + // ================================ - // #region - checkState + // ================================ + // #region - State + // ================================ + /** Throw {@link IllegalStateException} if the {@code condition} is false. */ public static void checkState(boolean condition) { checkCondition(condition, IllegalStateException::new); } + /** Throw {@link IllegalStateException} if the {@code condition} is false. */ public static void checkState(boolean condition, String errMsg) { checkCondition(condition, () -> new IllegalStateException(errMsg)); } - public static void checkState(boolean condition, Supplier messageSupplier) { + /** Throw {@link IllegalStateException} if the {@code condition} is false. */ + public static void checkState(boolean condition, @Nonnull Supplier messageSupplier) { checkCondition(condition, () -> new IllegalStateException(messageSupplier.get())); } + /** Throw {@link IllegalStateException} if the {@code condition} is false. */ public static void checkState(boolean condition, String format, Object... args) { checkCondition(condition, () -> new IllegalStateException(String.format(format, args))); } + // ================================ // #endregion + // ================================ - // #region - checkNotNull + // ================================ + // #region - NotNull + // ================================ + /** Throw {@link NullPointerException} if the {@code obj} is null. */ public static void checkNotNull(T obj) { checkCondition(obj != null, NullPointerException::new); } + /** Throw {@link NullPointerException} if the {@code obj} is null. */ public static void checkNotNull(T obj, String errMsg) { checkCondition(obj != null, () -> new NullPointerException(errMsg)); } - public static void checkNotNull(T obj, Supplier messageSupplier) { + /** Throw {@link NullPointerException} if the {@code obj} is null. */ + public static void checkNotNull(T obj, @Nonnull Supplier messageSupplier) { checkCondition(obj != null, () -> new NullPointerException(messageSupplier.get())); } + /** Throw {@link NullPointerException} if the {@code obj} is null. */ public static void checkNotNull(T obj, String format, Object... args) { checkCondition(obj != null, () -> new NullPointerException(String.format(format, args))); } + // ================================ // #endregion + // ================================ - // #region - checkCondition + // ================================ + // #region - Exists + // ================================ + + /** Throw {@link DataNotExistsException} if the {@code obj} is null. */ + public static T checkExists(T obj) + throws DataNotExistsException { + checkCondition(Objects.nonNull(obj), DataNotExistsException::new); + return obj; + } + + /** Throw {@link DataNotExistsException} if the {@code obj} is null. */ + public static T checkExists(T obj, String message) + throws DataNotExistsException { + checkCondition(Objects.nonNull(obj), () -> new DataNotExistsException(message)); + return obj; + } + + /** Throw {@link DataNotExistsException} if the {@code obj} is null. */ + public static T checkExists(T obj, @Nonnull Supplier messageSupplier) + throws DataNotExistsException { + checkCondition(Objects.nonNull(obj), () -> new DataNotExistsException(messageSupplier.get())); + return obj; + } + + /** Throw {@link DataNotExistsException} if the {@code obj} is null. */ + public static T checkExists(T obj, String format, Object... args) + throws DataNotExistsException { + checkCondition(Objects.nonNull(obj), () -> new DataNotExistsException(String.format(format, args))); + return obj; + } + + /** Throw {@link DataNotExistsException} if the {@code optional} is present. */ + public static T checkExists(@Nonnull Optional optional) + throws DataNotExistsException { + checkCondition(optional.isPresent(), DataNotExistsException::new); + return optional.get(); + } + + /** Throw {@link DataNotExistsException} if the {@code optional} is present. */ + public static T checkExists(@Nonnull Optional optional, String message) + throws DataNotExistsException { + checkCondition(optional.isPresent(), () -> new DataNotExistsException(message)); + return optional.get(); + } + + /** Throw {@link DataNotExistsException} if the {@code optional} is present. */ + public static T checkExists(@Nonnull Optional optional, @Nonnull Supplier messageSupplier) + throws DataNotExistsException { + checkCondition(optional.isPresent(), () -> new DataNotExistsException(messageSupplier.get())); + return optional.get(); + } + + /** Throw {@link DataNotExistsException} if the {@code optional} is present. */ + public static T checkExists(@Nonnull Optional optional, String format, Object... args) + throws DataNotExistsException { + checkCondition(optional.isPresent(), () -> new DataNotExistsException(String.format(format, args))); + return optional.get(); + } + + // ================================ + // #endregion - Exists + // ================================ + + // ================================ + // #region - AffectedRows + // ================================ + + public static void checkAffectedRows(int expectedValue, int result) { + checkAffectedRows(expectedValue, result, + "The number of rows affected is expected to be %d, but is: %d", expectedValue, result); + } + + public static void checkAffectedRows(int expectedValue, int result, String message) { + checkCondition(expectedValue == result, () -> new DataOperationResultException(message)); + } + + public static void checkAffectedRows(int expectedValue, int result, + @Nonnull Supplier messageSupplier) { + checkCondition(expectedValue == result, + () -> new DataOperationResultException(messageSupplier.get())); + } + + public static void checkAffectedRows(int expectedValue, int result, String format, Object... args) { + checkCondition(expectedValue == result, + () -> new DataOperationResultException(String.format(format, args))); + } + + public static void checkAffectedRows(long expectedValue, long result) { + checkAffectedRows(expectedValue, result, + "The number of rows affected is expected to be %d, but is: %d", expectedValue, result); + } + + public static void checkAffectedRows(long expectedValue, long result, String message) { + checkCondition(expectedValue == result, () -> new DataOperationResultException(message)); + } + + public static void checkAffectedRows(long expectedValue, long result, + @Nonnull Supplier messageSupplier) { + checkCondition(expectedValue == result, + () -> new DataOperationResultException(messageSupplier.get())); + } + + public static void checkAffectedRows(long expectedValue, long result, String format, Object... args) { + checkCondition(expectedValue == result, + () -> new DataOperationResultException(String.format(format, args))); + } + + public static void checkAffectedOneRow(int result) { + checkAffectedRows(1, result, + () -> "The number of rows affected is expected to be 1, but is: " + result); + } + + public static void checkAffectedOneRow(int result, String message) { + checkAffectedRows(1, result, message); + } + + public static void checkAffectedOneRow(int result, @Nonnull Supplier messageSupplier) { + checkAffectedRows(1, result, messageSupplier); + } + + public static void checkAffectedOneRow(int result, String format, Object... args) { + checkAffectedRows(1, result, format, args); + } + + public static void checkAffectedOneRow(long result) { + checkAffectedRows(1L, result, + () -> "The number of rows affected is expected to be 1, but is: " + result); + } + + public static void checkAffectedOneRow(long result, String message) { + checkAffectedRows(1L, result, message); + } + + public static void checkAffectedOneRow(long result, @Nonnull Supplier messageSupplier) { + checkAffectedRows(1L, result, messageSupplier); + } + + public static void checkAffectedOneRow(long result, String format, Object... args) { + checkAffectedRows(1L, result, format, args); + } + + // ================================ + // #endregion - AffectedRows + // ================================ + + // ================================ + // #region - Condition + // ================================ public static void checkCondition(boolean condition, @Nonnull Supplier e) throws T { @@ -123,13 +286,19 @@ public class AssertTools { } } + // ================================ // #endregion + // ================================ - // #region - private constructor + // ================================ + // #region - constructor + // ================================ private AssertTools() { throw new IllegalStateException("Utility class"); } + // ================================ // #endregion + // ================================ } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/BigDecimals.java b/src/main/java/xyz/zhouxy/plusone/commons/util/BigDecimals.java index 90a707e..1b006c7 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/BigDecimals.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/BigDecimals.java @@ -17,21 +17,31 @@ package xyz.zhouxy.plusone.commons.util; import java.math.BigDecimal; -import javax.annotation.Nullable; -import com.google.common.base.Preconditions; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod; +/** + * BigDecimals + * + *

+ * BigDecimal 工具类 + *

+ * + * @author ZhouXY + * @since 0.1.0 + */ public class BigDecimals { public static boolean equalsValue(@Nullable BigDecimal a, @Nullable BigDecimal b) { - return (a == b) || (a != null && a.compareTo(b) == 0); + return (a == b) || (a != null && b != null && a.compareTo(b) == 0); } public static boolean gt(BigDecimal a, BigDecimal b) { - Preconditions.checkNotNull(a, "Parameter could not be null."); - Preconditions.checkNotNull(b, "Parameter could not be null."); + AssertTools.checkNotNull(a, "Parameter could not be null."); + AssertTools.checkNotNull(b, "Parameter could not be null."); return (a != b) && (a.compareTo(b) > 0); } @@ -40,8 +50,8 @@ public class BigDecimals { } public static boolean lt(BigDecimal a, BigDecimal b) { - Preconditions.checkNotNull(a, "Parameter could not be null."); - Preconditions.checkNotNull(b, "Parameter could not be null."); + AssertTools.checkNotNull(a, "Parameter could not be null."); + AssertTools.checkNotNull(b, "Parameter could not be null."); return (a != b) && (a.compareTo(b) < 0); } @@ -49,6 +59,25 @@ public class BigDecimals { return lt(a, b) || equalsValue(a, b); } + public static BigDecimal sum(final BigDecimal... numbers) { + if (ArrayTools.isNullOrEmpty(numbers)) { + return BigDecimal.ZERO; + } + BigDecimal result = BigDecimals.nullToZero(numbers[0]); + for (int i = 1; i < numbers.length; i++) { + BigDecimal value = numbers[i]; + if (value != null) { + result = result.add(value); + } + } + return result; + } + + @Nonnull + public static BigDecimal nullToZero(@Nullable final BigDecimal val) { + return val != null ? val : BigDecimal.ZERO; + } + @StaticFactoryMethod(BigDecimal.class) public static BigDecimal of(final String val) { return (StringTools.isNotBlank(val)) ? new BigDecimal(val) : BigDecimal.ZERO; diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/ConcurrentHashMapTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/ConcurrentHashMapTools.java deleted file mode 100644 index c36ecf0..0000000 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/ConcurrentHashMapTools.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.util; - -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; - -import xyz.zhouxy.plusone.commons.base.JRE; -import xyz.zhouxy.plusone.commons.collection.SafeConcurrentHashMap; - -/** - * ConcurrentHashMapTools - * - *

- * Java 8 的 {@link ConcurrentHashMap#computeIfAbsent(Object, Function)} 方法有 bug, - * 可使用这个工具类的 {@link computeIfAbsentForJava8} 进行替换。 - * - *

- * NOTE: 方法来自Dubbo,见:issues#2349 - * - * @author ZhouXY - * @since 1.0 - * @see ConcurrentHashMap - * @see SafeConcurrentHashMap - */ -public class ConcurrentHashMapTools { - - public static V computeIfAbsent( - ConcurrentHashMap map, final K key, // NOSONAR - final Function mappingFunction) { - Objects.requireNonNull(map, "map"); - return JRE.isJava8() - ? computeIfAbsentForJava8(map, key, mappingFunction) - : map.computeIfAbsent(key, mappingFunction); - } - - public static V computeIfAbsentForJava8( - ConcurrentHashMap map, final K key, // NOSONAR - final Function mappingFunction) { - Objects.requireNonNull(key); - Objects.requireNonNull(mappingFunction); - V v = map.get(key); - if (null == v) { - v = mappingFunction.apply(key); - if (null == v) { - return null; - } - final V res = map.putIfAbsent(key, v); - if (null != res) { - return res; - } - } - return v; - } - - private ConcurrentHashMapTools() { - throw new IllegalStateException("Utility class"); - } -} diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java index c2597fe..e580706 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/DateTimeTools.java @@ -16,24 +16,23 @@ package xyz.zhouxy.plusone.commons.util; +import static java.time.temporal.ChronoField.*; + import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.Month; +import java.time.Year; import java.time.YearMonth; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; +import java.time.chrono.IsoChronology; import java.util.Calendar; import java.util.Date; import java.util.TimeZone; -import javax.annotation.Nonnull; - -import com.google.common.cache.CacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.LoadingCache; +import com.google.common.collect.Range; import xyz.zhouxy.plusone.commons.time.Quarter; import xyz.zhouxy.plusone.commons.time.YearQuarter; @@ -45,86 +44,35 @@ import xyz.zhouxy.plusone.commons.time.YearQuarter; */ public class DateTimeTools { - /** - * 缓存时间格式化器 - */ - private static final LoadingCache DATE_TIME_FORMATTER_CACHE = CacheBuilder.newBuilder() - .maximumSize(20) - .build(new CacheLoader() { - @Override - public DateTimeFormatter load(@Nonnull String pattern) throws Exception { - return DateTimeFormatter.ofPattern(pattern); - } - }); + // #region - toString - /** - * 获取时间格式化器 - * - * @param pattern 时间格式 - * @return 时间格式化器 - */ - public static DateTimeFormatter getDateTimeFormatter(String pattern) { - return DATE_TIME_FORMATTER_CACHE.getUnchecked(pattern); + public static String toYearString(int year) { + return Integer.toString(YEAR.checkValidIntValue(year)); } - /** - * 将日期时间转换为指定格式的字符串 - * - * @param pattern 时间格式 - * @param dateTime 日期时间 - * @return 格式化的字符串 - */ - public static String toString(String pattern, ZonedDateTime dateTime) { - return getDateTimeFormatter(pattern).format(dateTime); + public static String toYearString(Year year) { + return year.toString(); } - /** - * 将时间戳转换为指定格式的字符串,使用系统默认时区 - * - * @param pattern 时间格式 - * @param instant 时间戳 - * @return 格式化的字符串 - */ - public static String toString(String pattern, Instant instant) { - ZonedDateTime dateTime = instant.atZone(ZoneId.systemDefault()); - return toString(pattern, dateTime); + public static String toMonthStringM(int monthValue) { + return Integer.toString(MONTH_OF_YEAR.checkValidIntValue(monthValue)); } - /** - * 将时间戳转换为指定格式的字符串,使用指定时区 - * - * @param pattern 时间格式 - * @param instant 时间戳 - * @param zone 时区 - * @return 格式化的字符串 - */ - public static String toString(String pattern, Instant instant, ZoneId zone) { - ZonedDateTime dateTime = instant.atZone(zone); - return toString(pattern, dateTime); + public static String toMonthStringMM(int monthValue) { + return String.format("%02d", MONTH_OF_YEAR.checkValidIntValue(monthValue)); } - /** - * 指定格式,返回当前时间戳对应的字符串 - * - * @param pattern 时间格式 - * @return 格式化的字符串 - */ - public static String nowStr(String pattern) { - return toString(pattern, ZonedDateTime.now()); + public static String toMonthStringM(Month month) { + return Integer.toString(month.getValue()); } - /** - * 指定格式,返回当前时间戳对应的字符串,使用指定时区 - * - * @param pattern 时间格式 - * @param zone 时区 - * @return 格式化的字符串 - */ - public static String nowStr(String pattern, ZoneId zone) { - return toString(pattern, Instant.now().atZone(zone)); + public static String toMonthStringMM(Month month) { + return String.format("%02d", month.getValue()); } - // toDate + // #endregion + + // #region - toDate /** * 将时间戳转换为 {@link Date} 对象 @@ -189,7 +137,9 @@ public class DateTimeTools { return Date.from(ZonedDateTime.of(localDate, localTime, zone).toInstant()); } - // toInstant + // #endregion + + // #region - toInstant /** * 将时间戳转换为 {@link Instant} 对象 @@ -226,9 +176,7 @@ public class DateTimeTools { * * @param zonedDateTime {@link ZonedDateTime} 对象 * @return {@link Instant} 对象 - * @deprecated 请使用 {@link ZonedDateTime#toInstant()} 方法 */ - @Deprecated public static Instant toInstant(ZonedDateTime zonedDateTime) { // NOSONAR return zonedDateTime.toInstant(); } @@ -244,7 +192,9 @@ public class DateTimeTools { return ZonedDateTime.of(localDateTime, zone).toInstant(); } - // toZonedDateTime + // #endregion + + // #region - toZonedDateTime /** * 获取时间戳在指定时区的地区时间。 @@ -330,15 +280,14 @@ public class DateTimeTools { * @param localDateTime 地区时间 * @param zone 时区 * @return 带时区的地区时间 - * - * @deprecated 使用 {@link ZonedDateTime#of(LocalDateTime, ZoneId)} */ - @Deprecated - public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zone) { // NOSONAR + public static ZonedDateTime toZonedDateTime(LocalDateTime localDateTime, ZoneId zone) { return ZonedDateTime.of(localDateTime, zone); } - // toLocalDateTime + // #endregion + + // #region - toLocalDateTime /** * 获取时间戳在指定时区的地区时间。 @@ -406,9 +355,11 @@ public class DateTimeTools { return LocalDateTime.ofInstant(zonedDateTime.toInstant(), zone); } + // #endregion + // ==================== - // toJodaInstant + // #region - toJodaInstant /** * 将 {@link java.time.Instant} 转换为 {@link org.joda.time.Instant} @@ -441,7 +392,9 @@ public class DateTimeTools { return toJodaInstant(java.time.ZonedDateTime.of(localDateTime, zone)); } - // toJavaInstant + // #endregion + + // #region - toJavaInstant /** * 将 {@link org.joda.time.Instant} 对象转换为 {@link java.time.Instant} 对象 @@ -479,7 +432,9 @@ public class DateTimeTools { return toJavaInstant(localDateTime.toDateTime(zone)); } - // toJodaDateTime + // #endregion + + // #region - toJodaDateTime /** * 将 Java 中表示日期时间的 {@link java.time.ZonedDateTime} 对象 @@ -506,7 +461,7 @@ public class DateTimeTools { public static org.joda.time.DateTime toJodaDateTime( java.time.LocalDateTime localDateTime, java.time.ZoneId zone) { - org.joda.time.DateTimeZone dateTimeZone = toJodaTime(zone); + org.joda.time.DateTimeZone dateTimeZone = toJodaZone(zone); return toJodaInstant(ZonedDateTime.of(localDateTime, zone).toInstant()).toDateTime(dateTimeZone); } @@ -520,11 +475,13 @@ public class DateTimeTools { public static org.joda.time.DateTime toJodaDateTime( java.time.Instant instant, java.time.ZoneId zone) { - org.joda.time.DateTimeZone dateTimeZone = toJodaTime(zone); + org.joda.time.DateTimeZone dateTimeZone = toJodaZone(zone); return toJodaInstant(instant).toDateTime(dateTimeZone); } - // toZonedDateTime + // #endregion + + // #region - toZonedDateTime /** * 将 joda-time 中带时区的日期时间,转换为 java.time 中带时区的日期时间 @@ -568,7 +525,9 @@ public class DateTimeTools { return toJavaInstant(instant).atZone(zone); } - // toJodaLocalDateTime + // #endregion + + // #region - toJodaLocalDateTime /** * 将 {@link java.time.LocalDateTime} 转换为 {@link org.joda.time.LocalDateTime} @@ -578,11 +537,13 @@ public class DateTimeTools { */ public static org.joda.time.LocalDateTime toJodaLocalDateTime(java.time.LocalDateTime localDateTime) { java.time.ZoneId javaZone = java.time.ZoneId.systemDefault(); - org.joda.time.DateTimeZone jodaZone = toJodaTime(javaZone); + org.joda.time.DateTimeZone jodaZone = toJodaZone(javaZone); return toJodaInstant(localDateTime, javaZone).toDateTime(jodaZone).toLocalDateTime(); } - // toJavaLocalDateTime + // #endregion + + // #region - toJavaLocalDateTime /** * 将 {@link org.joda.time.LocalDateTime} 转换为 {@link java.time.LocalDateTime} @@ -596,6 +557,10 @@ public class DateTimeTools { return toJavaInstant(localDateTime, jodaZone).atZone(javaZone).toLocalDateTime(); } + // #endregion + + // #region - ZoneId <--> DateTimeZone + /** * 转换 Java API 和 joda-time API 表示时区的对象 * @@ -612,11 +577,13 @@ public class DateTimeTools { * @param zone Java API 中表示时区的对象 * @return joda-time API 中表示时区的对象 */ - public static org.joda.time.DateTimeZone toJodaTime(java.time.ZoneId zone) { + public static org.joda.time.DateTimeZone toJodaZone(java.time.ZoneId zone) { return org.joda.time.DateTimeZone.forID(zone.getId()); } - // getQuarter + // #endregion + + // #region - YearQuarter & Quarter /** * 获取指定日期所在季度 @@ -679,6 +646,44 @@ public class DateTimeTools { return YearQuarter.of(date); } + // #endregion + + // ================================ + // #region - others + // ================================ + + public static LocalDate startDateOfYear(int year) { + return LocalDate.ofYearDay(year, 1); + } + + public static LocalDate endDateOfYear(int year) { + return LocalDate.of(year, 12, 31); + } + + public static LocalDateTime startOfNextDate(LocalDate date) { + return date.plusDays(1L).atStartOfDay(); + } + + public static ZonedDateTime startOfNextDate(LocalDate date, ZoneId zone) { + return date.plusDays(1L).atStartOfDay(zone); + } + + public static Range toDateTimeRange(LocalDate date) { + return Range.closedOpen(date.atStartOfDay(), startOfNextDate(date)); + } + + public static Range toDateTimeRange(LocalDate date, ZoneId zone) { + return Range.closedOpen(date.atStartOfDay(zone), startOfNextDate(date, zone)); + } + + public static boolean isLeapYear(int year) { + return IsoChronology.INSTANCE.isLeapYear(year); + } + + // ================================ + // #endregion - others + // ================================ + /** * 私有构造方法,明确标识该常量类的作用。 */ diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/EnumTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/EnumTools.java index 428b29c..ba1f76a 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/EnumTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/EnumTools.java @@ -18,10 +18,9 @@ package xyz.zhouxy.plusone.commons.util; import java.util.function.Supplier; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.common.base.Preconditions; - /** * 枚举工具类 * @@ -36,44 +35,57 @@ public final class EnumTools { /** * 通过 ordinal 获取枚举实例 * - * @param 枚举的类型 - * @param clazz 枚举的类型信息 - * @param ordinal 序号 + * @param 枚举的类型 + * @param enumType 枚举的类型信息 + * @param ordinal 序号 * @return 枚举对象 * @deprecated 不推荐使用枚举的 ordinal。 */ @Deprecated - public static > E valueOf(Class clazz, int ordinal) { // NOSONAR 该方法弃用,但不删掉 - Preconditions.checkNotNull(clazz, "Clazz must not be null."); - E[] values = clazz.getEnumConstants(); + private static > E valueOfInternal(@Nonnull Class enumType, int ordinal) { // NOSONAR 该方法弃用,但不删掉 + E[] values = enumType.getEnumConstants(); AssertTools.checkCondition((ordinal >= 0 && ordinal < values.length), - () -> new EnumConstantNotPresentException(clazz, Integer.toString(ordinal))); + () -> new EnumConstantNotPresentException(enumType, Integer.toString(ordinal))); return values[ordinal]; } /** * 通过 ordinal 获取枚举实例 * - * @param 枚举的类型 - * @param clazz 枚举的类型信息 - * @param ordinal 序号 - * @param defaultValue 默认值 + * @param 枚举的类型 + * @param enumType 枚举的类型信息 + * @param ordinal 序号 * @return 枚举对象 * @deprecated 不推荐使用枚举的 ordinal。 */ @Deprecated - public static > E valueOf(Class clazz, @Nullable Integer ordinal, E defaultValue) { // NOSONAR 该方法弃用,但不删掉 - if (null == ordinal) { - return defaultValue; - } - return valueOf(clazz, ordinal); + public static > E valueOf(Class enumType, int ordinal) { // NOSONAR 该方法弃用,但不删掉 + AssertTools.checkNotNull(enumType, "Enum type must not be null."); + return valueOfInternal(enumType, ordinal); } /** * 通过 ordinal 获取枚举实例 * * @param 枚举的类型 - * @param clazz 枚举的类型信息 + * @param enumType 枚举的类型信息 + * @param ordinal 序号 + * @param defaultValue 默认值 + * @return 枚举对象 + * @deprecated 不推荐使用枚举的 ordinal。 + */ + @Deprecated + public static > E valueOf(Class enumType, // NOSONAR 该方法弃用,但不删掉 + @Nullable Integer ordinal, E defaultValue) { + AssertTools.checkNotNull(enumType); + return null == ordinal ? defaultValue : valueOfInternal(enumType, ordinal); + } + + /** + * 通过 ordinal 获取枚举实例 + * + * @param 枚举的类型 + * @param enumType 枚举的类型信息 * @param ordinal 序号 * @param defaultValue 默认值 * @return 枚举对象 @@ -81,29 +93,28 @@ public final class EnumTools { */ @Deprecated public static > E getValueOrDefault( // NOSONAR 该方法弃用,但不删掉 - Class clazz, + Class enumType, @Nullable Integer ordinal, Supplier defaultValue) { - if (null == ordinal) { - return defaultValue.get(); - } - return valueOf(clazz, ordinal); + AssertTools.checkNotNull(enumType); + AssertTools.checkNotNull(defaultValue); + return null == ordinal ? defaultValue.get() : valueOfInternal(enumType, ordinal); } /** * 通过 ordinal 获取枚举实例 * - * @param 枚举的类型 - * @param clazz 枚举的类型信息 - * @param ordinal 序号 + * @param 枚举的类型 + * @param enumType 枚举的类型信息 + * @param ordinal 序号 * @return 枚举对象 * @deprecated 不推荐使用枚举的 ordinal。 */ @Deprecated - public static > E getValueOrDefault(Class clazz, @Nullable Integer ordinal) { // NOSONAR 该方法弃用,但不删掉 - return getValueOrDefault(clazz, ordinal, () -> { - Preconditions.checkNotNull(clazz, "Clazz must not be null."); - E[] values = clazz.getEnumConstants(); + public static > E getValueOrDefault(Class enumType, @Nullable Integer ordinal) { // NOSONAR 该方法弃用,但不删掉 + return getValueOrDefault(enumType, ordinal, () -> { + AssertTools.checkNotNull(enumType, "Enum type must not be null."); + E[] values = enumType.getEnumConstants(); return values[0]; }); } @@ -111,69 +122,89 @@ public final class EnumTools { /** * 通过 ordinal 获取枚举实例 * - * @param 枚举的类型 - * @param clazz 枚举的类型信息 - * @param ordinal 序号 + * @param 枚举的类型 + * @param enumType 枚举的类型信息 + * @param ordinal 序号 * @return 枚举对象 * @deprecated 不推荐使用枚举的 ordinal。 */ @Deprecated - public static > E getValueNullable(Class clazz, @Nullable Integer ordinal) { // NOSONAR 该方法弃用,但不删掉 - return valueOf(clazz, ordinal, null); + public static > E getValueNullable(Class enumType, @Nullable Integer ordinal) { // NOSONAR 该方法弃用,但不删掉 + return valueOf(enumType, ordinal, null); } - public static > Integer checkOrdinal(Class clazz, Integer ordinal) { - Preconditions.checkNotNull(clazz, "Clazz must not be null."); - Preconditions.checkNotNull(ordinal, "Ordinal must not be null."); - E[] values = clazz.getEnumConstants(); - if (ordinal >= 0 && ordinal < values.length) { - return ordinal; - } - throw new EnumConstantNotPresentException(clazz, Integer.toString(ordinal)); + public static > Integer checkOrdinal(Class enumType, Integer ordinal) { + AssertTools.checkNotNull(enumType, "Enum type must not be null."); + AssertTools.checkNotNull(ordinal, "Ordinal must not be null."); + E[] values = enumType.getEnumConstants(); + AssertTools.checkCondition(ordinal >= 0 && ordinal < values.length, + () -> new EnumConstantNotPresentException(enumType, Integer.toString(ordinal))); + return ordinal; } /** * 校验枚举的 ordinal。 * - * @param 枚举类型 - * @param clazz 枚举类型 - * @param ordinal The ordinal + * @param 枚举类型 + * @param enumType 枚举类型 + * @param ordinal The ordinal * @return The ordinal */ @Nullable - public static > Integer checkOrdinalNullable(Class clazz, @Nullable Integer ordinal) { - return checkOrdinalOrDefault(clazz, ordinal, null); + public static > Integer checkOrdinalNullable(Class enumType, @Nullable Integer ordinal) { + return checkOrdinalOrDefault(enumType, ordinal, (Integer) null); } /** * 校验枚举的 ordinal,如果 ordinal 为 {@code null},则返回 {@code 0}。 * - * @param 枚举类型 - * @param clazz 枚举类型 - * @param ordinal The ordinal + * @param 枚举类型 + * @param enumType 枚举类型 + * @param ordinal The ordinal * @return The ordinal */ @Nullable - public static > Integer checkOrdinalOrDefault(Class clazz, @Nullable Integer ordinal) { - return checkOrdinalOrDefault(clazz, ordinal, 0); + public static > Integer checkOrdinalOrDefault(Class enumType, @Nullable Integer ordinal) { + return checkOrdinalOrDefault(enumType, ordinal, 0); } /** * 校验枚举的 ordinal,如果 ordinal 为 {@code null},则返回 {@code defaultValue}。 * - * @param 枚举类型 - * @param clazz 枚举类型 - * @param ordinal The ordinal + * @param 枚举类型 + * @param enumType 枚举类型 + * @param ordinal The ordinal * @return The ordinal */ @Nullable public static > Integer checkOrdinalOrDefault( - Class clazz, + Class enumType, @Nullable Integer ordinal, @Nullable Integer defaultValue) { - if (ordinal != null) { - return checkOrdinal(clazz, ordinal); - } - return defaultValue; + AssertTools.checkNotNull(enumType); + return checkOrdinalOrGetInternal(enumType, ordinal, () -> checkOrdinalOrDefaultInternal(enumType, defaultValue, null)); + } + + /** + * 仅对 {@code ordinal} 进行判断,不对 {@code defaultValue} 进行判断 + */ + @Nullable + private static > Integer checkOrdinalOrDefaultInternal( + @Nonnull Class enumType, + @Nullable Integer ordinal, + @Nullable Integer defaultValue) { + return ordinal != null + ? checkOrdinal(enumType, ordinal) + : defaultValue; + } + + @Nullable + private static > Integer checkOrdinalOrGetInternal( + @Nonnull Class enumType, + @Nullable Integer ordinal, + @Nonnull Supplier defaultValueSupplier) { + return ordinal != null + ? checkOrdinal(enumType, ordinal) + : defaultValueSupplier.get(); } } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/Enumeration.java b/src/main/java/xyz/zhouxy/plusone/commons/util/Enumeration.java index a6bfa8f..aee9e0b 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/Enumeration.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/Enumeration.java @@ -24,8 +24,6 @@ import java.util.Objects; import java.util.function.Function; import java.util.stream.Collectors; -import com.google.common.base.Preconditions; - import xyz.zhouxy.plusone.commons.annotation.StaticFactoryMethod; /** @@ -43,7 +41,7 @@ public abstract class Enumeration> // NOSONAR 暂不移 protected final String name; protected Enumeration(final int id, final String name) { - Preconditions.checkArgument(StringTools.isNotBlank(name), "Name of enumeration must has text."); + AssertTools.checkArgument(StringTools.isNotBlank(name), "Name of enumeration must has text."); this.id = id; this.name = name; } @@ -98,7 +96,7 @@ public abstract class Enumeration> // NOSONAR 暂不移 } public T get(int id) { - Preconditions.checkArgument(this.valueMap.containsKey(id), "[%s] 对应的值不存在", id); + AssertTools.checkArgument(this.valueMap.containsKey(id), "[%s] 对应的值不存在", id); return this.valueMap.get(id); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/IdGenerator.java b/src/main/java/xyz/zhouxy/plusone/commons/util/IdGenerator.java index 3301641..2bf8276 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/IdGenerator.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/IdGenerator.java @@ -17,12 +17,21 @@ package xyz.zhouxy.plusone.commons.util; import java.util.Map; +import java.util.Objects; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import com.google.common.annotations.Beta; - -@Beta +/** + * ID 生成器 + * + *

+ * 生成 UUID 和 修改版雪花ID(Seata 版本) + *

+ * + * @see UUID + * @see IdWorker + * @author ZhouXY + */ public class IdGenerator { // ===== UUID ===== @@ -40,6 +49,7 @@ public class IdGenerator { } public static String toSimpleString(UUID uuid) { + AssertTools.checkArgument(Objects.nonNull(uuid)); return (uuidDigits(uuid.getMostSignificantBits() >> 32, 8) + uuidDigits(uuid.getMostSignificantBits() >> 16, 4) + uuidDigits(uuid.getMostSignificantBits(), 4) + diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/IdWorker.java b/src/main/java/xyz/zhouxy/plusone/commons/util/IdWorker.java index e806cb2..20b59bc 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/IdWorker.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/IdWorker.java @@ -24,7 +24,26 @@ import java.util.concurrent.atomic.AtomicLong; import xyz.zhouxy.plusone.commons.exception.system.NoAvailableMacFoundException; - +/** + * Seata 提供的修改版雪花ID。 + *

+ * 大体思路为: + *

    + *
  1. 每个机器线程安全地生成序列,前面加上机器的id,这样就不会与其它机器的id相冲突。
  2. + *
  3. 时间戳作为序列的“预留位”,它更像是应用启动时最开始的序列的一部分,在一个时间戳里生成 4096 个 id 之后,直接生成下一个时间戳的 id。
  4. + *
+ *

+ *

+ * 详情见以下介绍: + *

+ *

+ * @author ZhouXY + */ public class IdWorker { /** diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/Numbers.java b/src/main/java/xyz/zhouxy/plusone/commons/util/Numbers.java index f79e72c..d8138f3 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/Numbers.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/Numbers.java @@ -17,12 +17,10 @@ package xyz.zhouxy.plusone.commons.util; import java.math.BigDecimal; +import java.math.BigInteger; import javax.annotation.Nonnull; - -import com.google.common.base.Preconditions; - -import xyz.zhouxy.plusone.commons.math.IntervalType; +import javax.annotation.Nullable; /** * Numbers @@ -31,7 +29,7 @@ import xyz.zhouxy.plusone.commons.math.IntervalType; */ public class Numbers { - // sum + // #region - sum public static int sum(final short... numbers) { int result = 0; @@ -73,105 +71,64 @@ public class Numbers { return result; } - public static BigDecimal sum(final BigDecimal... numbers) { - BigDecimal result = BigDecimals.of("0.00"); - for (BigDecimal number : numbers) { - result = result.add(number); + public static BigInteger sum(final BigInteger... numbers) { + if (ArrayTools.isNullOrEmpty(numbers)) { + return BigInteger.ZERO; + } + BigInteger result = Numbers.nullToZero(numbers[0]); + for (int i = 1; i < numbers.length; i++) { + BigInteger value = numbers[i]; + if (value != null) { + result = result.add(value); + } } return result; } - // between - - public static boolean between(int value, int min, int max) { - return between(value, min, max, IntervalType.CLOSED_OPEN); + public static BigDecimal sum(final BigDecimal... numbers) { + return BigDecimals.sum(numbers); } - public static boolean between(int value, int min, int max, IntervalType intervalType) { - final IntervalType intervalTypeToUse = intervalType != null - ? intervalType - : IntervalType.CLOSED_OPEN; - switch (intervalTypeToUse) { - case OPEN: - return min < value && value < max; - case CLOSED: - return min <= value && value <= max; - case OPEN_CLOSED: - return min < value && value <= max; - case CLOSED_OPEN: - default: - return min <= value && value < max; - } + // #endregion + + // #region - nullToZero + + public static byte nullToZero(@Nullable final Byte val) { + return val != null ? val : 0; } - public static boolean between(long value, long min, long max) { - return between(value, min, max, IntervalType.CLOSED_OPEN); + public static short nullToZero(@Nullable final Short val) { + return val != null ? val : 0; } - public static boolean between(long value, long min, long max, IntervalType intervalType) { - final IntervalType intervalTypeToUse = intervalType != null - ? intervalType - : IntervalType.CLOSED_OPEN; - switch (intervalTypeToUse) { - case OPEN: - return min < value && value < max; - case CLOSED: - return min <= value && value <= max; - case OPEN_CLOSED: - return min < value && value <= max; - case CLOSED_OPEN: - default: - return min <= value && value < max; - } + public static int nullToZero(@Nullable final Integer val) { + return val != null ? val : 0; } - public static boolean between(double value, double min, double max) { - return between(value, min, max, IntervalType.CLOSED_OPEN); + public static long nullToZero(@Nullable final Long val) { + return val != null ? val : 0L; } - public static boolean between(double value, double min, double max, IntervalType intervalType) { - final IntervalType intervalTypeToUse = intervalType != null - ? intervalType - : IntervalType.CLOSED_OPEN; - switch (intervalTypeToUse) { - case OPEN: - return min < value && value < max; - case CLOSED: - return min <= value && value <= max; - case OPEN_CLOSED: - return min < value && value <= max; - case CLOSED_OPEN: - default: - return min <= value && value < max; - } + public static float nullToZero(@Nullable final Float val) { + return val != null ? val : 0.0F; } - public static > boolean between(@Nonnull T value, T min, T max) { - return between(value, min, max, IntervalType.CLOSED_OPEN); + public static double nullToZero(@Nullable final Double val) { + return val != null ? val : 0.0; } - public static > boolean between(@Nonnull T value, T min, T max, IntervalType intervalType) { - Preconditions.checkArgument(value != null, "The value to valid connot be null."); - IntervalType intervalTypeToUse = intervalType != null - ? intervalType - : IntervalType.CLOSED_OPEN; - switch (intervalTypeToUse) { - case OPEN: - return (min == null || min.compareTo(value) < 0) - && (max == null || value.compareTo(max) < 0); - case CLOSED: - return (min == null || min.compareTo(value) <= 0) - && (max == null || value.compareTo(max) <= 0); - case OPEN_CLOSED: - return (min == null || min.compareTo(value) < 0) - && (max == null || value.compareTo(max) <= 0); - case CLOSED_OPEN: - default: - return (min == null || min.compareTo(value) <= 0) - && (max == null || value.compareTo(max) < 0); - } + @Nonnull + public static BigInteger nullToZero(@Nullable final BigInteger val) { + return val != null ? val : BigInteger.ZERO; } + @Nonnull + public static BigDecimal nullToZero(@Nullable final BigDecimal val) { + return BigDecimals.nullToZero(val); + } + + // #endregion + private Numbers() { throw new IllegalStateException("Utility class"); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java index 6afc578..07f54bf 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/RandomTools.java @@ -16,20 +16,48 @@ package xyz.zhouxy.plusone.commons.util; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.util.Objects; import java.util.Random; import java.util.concurrent.ThreadLocalRandom; import javax.annotation.Nonnull; +/** + * 随机工具类 + *

+ * 建议调用方自行维护 Random 对象 + *

+ * @author ZhouXY + */ public final class RandomTools { - public static final SecureRandom DEFAULT_SECURE_RANDOM = new SecureRandom(); + private static final SecureRandom DEFAULT_SECURE_RANDOM; + + static { + SecureRandom secureRandom = null; + try { + secureRandom = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器 + } + catch (NoSuchAlgorithmException e) { + secureRandom = new SecureRandom(); // 获取普通的安全随机数生成器 + } + DEFAULT_SECURE_RANDOM = secureRandom; + } public static final String CAPITAL_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static final String LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"; public static final String NUMBERS = "0123456789"; + public static SecureRandom defaultSecureRandom() { + return DEFAULT_SECURE_RANDOM; + } + + public static ThreadLocalRandom currentThreadLocalRandom() { + return ThreadLocalRandom.current(); + } + /** * 使用传入的随机数生成器,生成指定长度的字符串 * @@ -41,20 +69,20 @@ public final class RandomTools { * @return 随机字符串 */ public static String randomStr(@Nonnull Random random, @Nonnull char[] sourceCharacters, int length) { - AssertTools.checkArgumentNotNull(random, "Random cannot be null."); - AssertTools.checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); + AssertTools.checkArgument(Objects.nonNull(random), "Random cannot be null."); + AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null."); AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(random, sourceCharacters, length); } public static String randomStr(@Nonnull char[] sourceCharacters, int length) { - AssertTools.checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); + AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null."); AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length); } public static String secureRandomStr(@Nonnull char[] sourceCharacters, int length) { - AssertTools.checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); + AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null."); AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length); } @@ -70,20 +98,20 @@ public final class RandomTools { * @return 随机字符串 */ public static String randomStr(@Nonnull Random random, @Nonnull String sourceCharacters, int length) { - AssertTools.checkArgumentNotNull(random, "Random cannot be null."); - AssertTools.checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); + AssertTools.checkArgument(Objects.nonNull(random), "Random cannot be null."); + AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null."); AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(random, sourceCharacters, length); } public static String randomStr(@Nonnull String sourceCharacters, int length) { - AssertTools.checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); + AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null."); AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(ThreadLocalRandom.current(), sourceCharacters, length); } public static String secureRandomStr(@Nonnull String sourceCharacters, int length) { - AssertTools.checkArgumentNotNull(sourceCharacters, "Source characters cannot be null."); + AssertTools.checkArgument(Objects.nonNull(sourceCharacters), "Source characters cannot be null."); AssertTools.checkArgument(length >= 0, "The length should be greater than or equal to zero."); return randomStrInternal(DEFAULT_SECURE_RANDOM, sourceCharacters, length); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java index 141a044..53551f8 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/RegexTools.java @@ -26,8 +26,6 @@ import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import com.google.common.base.Preconditions; - /** * 封装一些常用的正则操作,并可以缓存 {@link Pattern} 实例以复用(最多缓存大概 256 个)。 * @@ -49,7 +47,7 @@ public final class RegexTools { * @return {@link Pattern} 实例 */ public static Pattern getPattern(final String pattern, final boolean cachePattern) { - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(pattern); return cachePattern ? cacheAndGetPatternInternal(pattern) : getPatternInternal(pattern); } @@ -60,7 +58,7 @@ public final class RegexTools { * @return {@link Pattern} 实例 */ public static Pattern getPattern(final String pattern) { - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(pattern); return getPatternInternal(pattern); } @@ -72,8 +70,8 @@ public final class RegexTools { * @return {@link Pattern} 实例数组 */ public static Pattern[] getPatterns(final String[] patterns, final boolean cachePattern) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); return cachePattern ? cacheAndGetPatternsInternal(patterns) : getPatternsInternal(patterns); @@ -86,27 +84,11 @@ public final class RegexTools { * @return {@link Pattern} 实例数组 */ public static Pattern[] getPatterns(final String[] patterns) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); return getPatternsInternal(patterns); } - /** - * 手动缓存 Pattern 实例。 - * - * @param pattern 要缓存的 {@link Pattern} 实例 - * @return 缓存的 Pattern 实例。如果缓存已满,则返回 {@code null}。 - */ - public static Pattern cachePattern(final Pattern pattern) { - Preconditions.checkNotNull(pattern, "The pattern can not be null."); - if (PATTERN_CACHE.size() >= MAX_CACHE_SIZE) { - return null; - } - final String patternStr = pattern.pattern(); - final Pattern pre = PATTERN_CACHE.putIfAbsent(patternStr, pattern); - return pre != null ? pre : pattern; - } - /** * 判断 {@code input} 是否匹配 {@code pattern}。 * @@ -115,7 +97,7 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matches(@Nullable final CharSequence input, final Pattern pattern) { - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(pattern); return matchesInternal(input, pattern); } @@ -127,8 +109,8 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matchesOne(@Nullable final CharSequence input, final Pattern[] patterns) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); return matchesOneInternal(input, patterns); } @@ -140,8 +122,8 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matchesAll(@Nullable final CharSequence input, final Pattern[] patterns) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); return matchesAllInternal(input, patterns); } @@ -155,7 +137,7 @@ public final class RegexTools { */ public static boolean matches(@Nullable final CharSequence input, final String pattern, final boolean cachePattern) { - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(pattern); Pattern p = cachePattern ? cacheAndGetPatternInternal(pattern) : getPatternInternal(pattern); @@ -170,7 +152,7 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matches(@Nullable final CharSequence input, final String pattern) { - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(pattern); return matchesInternal(input, getPatternInternal(pattern)); } @@ -184,8 +166,8 @@ public final class RegexTools { */ public static boolean matchesOne(@Nullable final CharSequence input, final String[] patterns, final boolean cachePattern) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); final Pattern[] patternSet = cachePattern ? cacheAndGetPatternsInternal(patterns) : getPatternsInternal(patterns); @@ -200,8 +182,8 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matchesOne(@Nullable final CharSequence input, final String[] patterns) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); final Pattern[] patternSet = getPatternsInternal(patterns); return matchesOneInternal(input, patternSet); } @@ -216,8 +198,8 @@ public final class RegexTools { */ public static boolean matchesAll(@Nullable final CharSequence input, final String[] patterns, final boolean cachePattern) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); final Pattern[] patternSet = cachePattern ? cacheAndGetPatternsInternal(patterns) : getPatternsInternal(patterns); @@ -232,8 +214,8 @@ public final class RegexTools { * @return 判断结果 */ public static boolean matchesAll(@Nullable final CharSequence input, final String[] patterns) { - Preconditions.checkNotNull(patterns); - Preconditions.checkArgument(allNotNull(patterns)); + AssertTools.checkNotNull(patterns); + AssertTools.checkArgument(allNotNull(patterns)); final Pattern[] patternSet = getPatternsInternal(patterns); return matchesAllInternal(input, patternSet); } @@ -246,8 +228,8 @@ public final class RegexTools { * @return 结果 */ public static Matcher getMatcher(final CharSequence input, final Pattern pattern) { - Preconditions.checkNotNull(input); - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(input); + AssertTools.checkNotNull(pattern); return pattern.matcher(input); } @@ -260,8 +242,8 @@ public final class RegexTools { * @return 结果 */ public static Matcher getMatcher(final CharSequence input, final String pattern, boolean cachePattern) { - Preconditions.checkNotNull(input); - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(input); + AssertTools.checkNotNull(pattern); final Pattern p = cachePattern ? cacheAndGetPatternInternal(pattern) : getPatternInternal(pattern); @@ -276,8 +258,8 @@ public final class RegexTools { * @return 结果 */ public static Matcher getMatcher(final CharSequence input, final String pattern) { - Preconditions.checkNotNull(input); - Preconditions.checkNotNull(pattern); + AssertTools.checkNotNull(input); + AssertTools.checkNotNull(pattern); return getPatternInternal(pattern).matcher(input); } @@ -287,7 +269,6 @@ public final class RegexTools { * 获取 {@link Pattern} 实例。 * * @param pattern 正则表达式 - * @param cachePattern 是否缓存 {@link Pattern} 实例 * @return {@link Pattern} 实例 */ @Nonnull diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/SnowflakeIdGenerator.java b/src/main/java/xyz/zhouxy/plusone/commons/util/SnowflakeIdGenerator.java index 383a6dd..c8af751 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/SnowflakeIdGenerator.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/SnowflakeIdGenerator.java @@ -18,13 +18,9 @@ package xyz.zhouxy.plusone.commons.util; import java.util.concurrent.TimeUnit; -import com.google.common.annotations.Beta; -import com.google.common.base.Preconditions; - /** - * Twitter_Snowflake + * Twitter 版雪花算法 */ -@Beta public class SnowflakeIdGenerator { // ==============================Fields=========================================== @@ -68,9 +64,6 @@ public class SnowflakeIdGenerator { /** 上次生成 ID 的时间截 */ private long lastTimestamp = -1L; - /** 锁对象 */ - private final Object lock = new Object(); - // ==============================Constructors===================================== /** @@ -80,9 +73,9 @@ public class SnowflakeIdGenerator { * @param datacenterId 数据中心ID (0~31) */ public SnowflakeIdGenerator(final long workerId, final long datacenterId) { - Preconditions.checkArgument((workerId <= MAX_WORKER_ID && workerId >= 0), + AssertTools.checkArgument((workerId <= MAX_WORKER_ID && workerId >= 0), "WorkerId can't be greater than %s or less than 0.", MAX_WORKER_ID); - Preconditions.checkArgument((datacenterId <= MAX_DATACENTER_ID && datacenterId >= 0), + AssertTools.checkArgument((datacenterId <= MAX_DATACENTER_ID && datacenterId >= 0), "DatacenterId can't be greater than %s or less than 0.", MAX_DATACENTER_ID); this.datacenterIdAndWorkerId = (datacenterId << DATACENTER_ID_SHIFT) | (workerId << WORKER_ID_SHIFT); @@ -94,51 +87,47 @@ public class SnowflakeIdGenerator { * * @return SnowflakeId */ - public long nextId() { - long timestamp; - synchronized (lock) { - timestamp = timeGen(); + public synchronized long nextId() { + long timestamp = timeGen(); - // 发生了回拨,此刻时间小于上次发号时间 - if (timestamp < lastTimestamp) { - long offset = lastTimestamp - timestamp; - if (offset <= 5) { - // 时间偏差大小小于5ms,则等待两倍时间 - try { - TimeUnit.MILLISECONDS.sleep(offset << 1); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - throw new IllegalStateException(e); - } - timestamp = timeGen(); - if (timestamp < lastTimestamp) { - // 还是小于,抛异常上报 - throwClockBackwardsEx(lastTimestamp, timestamp); - } - } else { + // 发生了回拨,此刻时间小于上次发号时间 + if (timestamp < lastTimestamp) { + long offset = lastTimestamp - timestamp; + if (offset <= 5) { + // 时间偏差大小小于5ms,则等待两倍时间 + try { + TimeUnit.MILLISECONDS.sleep(offset << 1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new IllegalStateException(e); + } + timestamp = timeGen(); + if (timestamp < lastTimestamp) { + // 还是小于,抛异常上报 throwClockBackwardsEx(lastTimestamp, timestamp); } + } else { + throwClockBackwardsEx(lastTimestamp, timestamp); } - - // 如果是同一时间生成的,则进行毫秒内序列 - if (lastTimestamp == timestamp) { - sequence = (sequence + 1) & SEQUENCE_MASK; - // 毫秒内序列溢出 - if (sequence == 0) { - // 阻塞到下一个毫秒,获得新的时间戳 - timestamp = tilNextMillis(lastTimestamp); - } - } - // 时间戳改变,毫秒内序列重置 - else { - sequence = 0L; - } - - // 上次生成 ID 的时间截 - lastTimestamp = timestamp; - } + // 如果是同一时间生成的,则进行毫秒内序列 + if (lastTimestamp == timestamp) { + sequence = (sequence + 1) & SEQUENCE_MASK; + // 毫秒内序列溢出 + if (sequence == 0) { + // 阻塞到下一个毫秒,获得新的时间戳 + timestamp = tilNextMillis(lastTimestamp); + } + } + // 时间戳改变,毫秒内序列重置 + else { + sequence = 0L; + } + + // 上次生成 ID 的时间截 + lastTimestamp = timestamp; + // 移位并通过或运算拼到一起组成64位的ID return ((timestamp - TWEPOCH) << TIMESTAMP_LEFT_SHIFT) | datacenterIdAndWorkerId | sequence; } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java b/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java index 951ead0..5dd5659 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/StringTools.java @@ -16,6 +16,8 @@ package xyz.zhouxy.plusone.commons.util; +import java.util.Objects; + import com.google.common.annotations.Beta; @Beta @@ -35,6 +37,15 @@ public class StringTools { return false; } + public static String repeat(String str, int times) { + return repeat(str, times, Integer.MAX_VALUE); + } + + public static String repeat(String str, int times, int maxLength) { + AssertTools.checkArgument(Objects.nonNull(str)); + return String.valueOf(ArrayTools.repeat(str.toCharArray(), times, maxLength)); + } + private StringTools() { throw new IllegalStateException("Utility class"); } diff --git a/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java b/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java index 83b8af9..10e8e7a 100644 --- a/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java +++ b/src/main/java/xyz/zhouxy/plusone/commons/util/TreeBuilder.java @@ -27,8 +27,6 @@ import java.util.stream.Collectors; import javax.annotation.Nullable; -import com.google.common.base.Preconditions; - /** * TreeBuilder * @@ -63,7 +61,7 @@ public class TreeBuilder { * @param nodes 平铺的节点列表 */ public List buildTree(Collection nodes) { - Preconditions.checkNotNull(nodes); + AssertTools.checkNotNull(nodes); return buildTreeInternal(nodes, this.defaultComparator); } @@ -80,7 +78,7 @@ public class TreeBuilder { * 仅影响调用 addChild 的顺序,如果操作对象本身对应的控制了子节点的顺序,无法影响其相关逻辑。 */ public List buildTree(Collection nodes, @Nullable Comparator comparator) { - Preconditions.checkNotNull(nodes); + AssertTools.checkNotNull(nodes); final Comparator c = (comparator != null) ? comparator : this.defaultComparator; return buildTreeInternal(nodes, c); } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/base/IWithCodeTests.java b/src/test/java/xyz/zhouxy/plusone/commons/base/IWithCodeTests.java new file mode 100644 index 0000000..92c7da8 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/base/IWithCodeTests.java @@ -0,0 +1,140 @@ +/* + * Copyright 2024-2025 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.base; + +import static org.junit.jupiter.api.Assertions.*; + +import javax.annotation.Nonnull; + +import org.junit.jupiter.api.Test; + +import xyz.zhouxy.plusone.commons.util.AssertTools; + +class IWithCodeTests { + + @Test + void equalsCode_SameCode_ReturnsTrue() { + assertTrue(WithCode.INSTANCE.isCodeEquals("testCode")); + Integer intCode = 0; + Long longCode = 0L; + assertTrue(WithIntCode.INSTANCE.isCodeEquals(intCode)); + assertTrue(WithLongCode.INSTANCE.isCodeEquals(intCode)); + assertTrue(WithLongCode.INSTANCE.isCodeEquals(longCode)); + + assertTrue(WithCode.INSTANCE.isSameCodeAs(WithCode.SAME_CODE_INSTANCE)); + assertTrue(WithIntCode.INSTANCE.isSameCodeAs(WithIntCode.SAME_CODE_INSTANCE)); + assertTrue(WithIntCode.INSTANCE.isSameCodeAs(WithLongCode.SAME_CODE_INSTANCE)); + assertTrue(WithLongCode.INSTANCE.isSameCodeAs(WithLongCode.SAME_CODE_INSTANCE)); + assertTrue(WithLongCode.INSTANCE.isSameCodeAs(WithIntCode.SAME_CODE_INSTANCE)); + } + + @Test + void equalsCode_DifferentCode_ReturnsFalse() { + assertFalse(WithCode.INSTANCE.isCodeEquals("wrongCode")); + Integer intCode = 108; + Long longCode = 108L; + assertFalse(WithIntCode.INSTANCE.isCodeEquals(intCode)); + assertFalse(WithLongCode.INSTANCE.isCodeEquals(intCode)); + assertFalse(WithLongCode.INSTANCE.isCodeEquals(longCode)); + + assertFalse(WithCode.INSTANCE.isSameCodeAs(WithCode.WRONG_CODE_INSTANCE)); + assertFalse(WithIntCode.INSTANCE.isSameCodeAs(WithIntCode.WRONG_CODE_INSTANCE)); + assertFalse(WithIntCode.INSTANCE.isSameCodeAs(WithLongCode.WRONG_CODE_INSTANCE)); + assertFalse(WithLongCode.INSTANCE.isSameCodeAs(WithLongCode.WRONG_CODE_INSTANCE)); + assertFalse(WithLongCode.INSTANCE.isSameCodeAs(WithIntCode.WRONG_CODE_INSTANCE)); + } + + @Test + @SuppressWarnings("null") + void equalsCode_NullCode_ReturnsFalse() { + assertFalse(WithCode.INSTANCE.isSameCodeAs((WithCode) null)); + assertFalse(WithCode.INSTANCE.isSameCodeAs((WithIntCode) null)); + assertFalse(WithCode.INSTANCE.isSameCodeAs((WithLongCode) null)); + + assertFalse(WithIntCode.INSTANCE.isSameCodeAs((WithCode) null)); + assertFalse(WithIntCode.INSTANCE.isSameCodeAs((WithIntCode) null)); + assertFalse(WithIntCode.INSTANCE.isSameCodeAs((WithLongCode) null)); + + assertFalse(WithLongCode.INSTANCE.isSameCodeAs((WithCode) null)); + assertFalse(WithLongCode.INSTANCE.isSameCodeAs((WithIntCode) null)); + assertFalse(WithLongCode.INSTANCE.isSameCodeAs((WithLongCode) null)); + + assertFalse(WithCode.INSTANCE.isCodeEquals((String) null)); + Integer intCode = null; + Long longCode = null; + assertThrows(NullPointerException.class, () -> WithIntCode.INSTANCE.isCodeEquals(intCode)); + assertThrows(NullPointerException.class, () -> WithLongCode.INSTANCE.isCodeEquals(intCode)); + assertThrows(NullPointerException.class, () -> WithLongCode.INSTANCE.isCodeEquals(longCode)); + } + + private static enum WithCode implements IWithCode { + INSTANCE("testCode"), + SAME_CODE_INSTANCE("testCode"), + WRONG_CODE_INSTANCE("wrongCode"), + ; + + @Nonnull + private final String code; + + WithCode(String code) { + AssertTools.checkNotNull(code); + this.code = code; + } + + @Override + @Nonnull + public String getCode() { + return code; + } + } + + private static enum WithIntCode implements IWithIntCode { + INSTANCE(0), + SAME_CODE_INSTANCE(0), + WRONG_CODE_INSTANCE(1), + ; + + private final int code; + + WithIntCode(int code) { + this.code = code; + } + + @Override + public int getCode() { + return code; + } + } + + private static enum WithLongCode implements IWithLongCode { + INSTANCE(0L), + SAME_CODE_INSTANCE(0L), + WRONG_CODE_INSTANCE(108L), + ; + + private final long code; + + WithLongCode(long code) { + this.code = code; + } + + @Override + public long getCode() { + return code; + } + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/base/RefTests.java b/src/test/java/xyz/zhouxy/plusone/commons/base/RefTests.java new file mode 100644 index 0000000..5ff8e22 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/base/RefTests.java @@ -0,0 +1,46 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.base; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +class RefTests { + + @Test + void testRef() { + Ref strRef = Ref.of("ZhouXY"); + assertTrue(strRef.checkValue("ZhouXY"::equals)); + assertFalse(strRef.checkValue("ZhouXY1"::equals)); + apply(strRef); + assertEquals("Hello ZhouXY", strRef.getValue()); + assertTrue(strRef.checkValue("Hello ZhouXY"::equals)); + log.info("strRef: {}", strRef); + + Ref intStringRef = Ref.of("108"); + Ref integerRef = intStringRef.transform(Integer::parseInt); + assertEquals(108, integerRef.getValue()); + } + + void apply(Ref strRef) { + strRef.transformValue(str -> "Hello " + str); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/collection/CollectionToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/collection/CollectionToolsTests.java new file mode 100644 index 0000000..97fdef5 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/collection/CollectionToolsTests.java @@ -0,0 +1,72 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; + +public class CollectionToolsTests { + @Test + void testIsEmpty() { + + List list = new ArrayList<>(); + assertTrue(CollectionTools.isEmpty(list)); + assertFalse(CollectionTools.isNotEmpty(list)); + + list.add("Test"); + assertFalse(CollectionTools.isEmpty(list)); + assertTrue(CollectionTools.isNotEmpty(list)); + + Map map = new HashMap<>(); + assertTrue(CollectionTools.isEmpty(map)); + assertFalse(CollectionTools.isNotEmpty(map)); + + map.put("2", 2); + assertFalse(CollectionTools.isEmpty(map)); + assertTrue(CollectionTools.isNotEmpty(map)); + } + + @Test + void testNullToEmpty() { + List list = Lists.newArrayList("Java", "C", "C++", "C#"); + assertSame(list, CollectionTools.nullToEmptyList(list)); + assertEquals(Collections.emptyList(), CollectionTools.nullToEmptyList(null)); + + Set set = Sets.newHashSet("Java", "C", "C++", "C#"); + assertSame(set, CollectionTools.nullToEmptySet(set)); + assertEquals(Collections.emptySet(), CollectionTools.nullToEmptySet(null)); + + Map map = ImmutableMap.of("K1", 1, "K2", 2, "K3", 3); + assertSame(map, CollectionTools.nullToEmptyMap(map)); + assertEquals(Collections.emptyMap(), CollectionTools.nullToEmptyMap(null)); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/constant/PatternConstsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/constant/PatternConstsTests.java new file mode 100644 index 0000000..88304d6 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/constant/PatternConstsTests.java @@ -0,0 +1,235 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.constant; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.regex.Matcher; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public // +class PatternConstsTests { + + // ================================ + // #region - BASIC_ISO_DATE + // ================================ + + @Test + void testBasicIsoDate_ValidDate() { + Matcher matcher = PatternConsts.BASIC_ISO_DATE.matcher("20241229"); + assertTrue(matcher.matches()); + + assertEquals("2024", matcher.group(1)); + assertEquals("12", matcher.group(2)); + assertEquals("29", matcher.group(3)); + assertEquals("2024", matcher.group("yyyy")); + assertEquals("12", matcher.group("MM")); + assertEquals("29", matcher.group("dd")); + + // LeapYearFeb29() + assertTrue(PatternConsts.BASIC_ISO_DATE.matcher("20200229").matches()); + + // BoundaryMin() + assertTrue(PatternConsts.BASIC_ISO_DATE.matcher("00000101").matches()); + + // BoundaryMax() + assertTrue(PatternConsts.BASIC_ISO_DATE.matcher("9999999991231").matches()); + } + + @ParameterizedTest + @ValueSource(strings = { + "20231301", // InvalidMonth + "20230230", // InvalidDay + "20210229", // NonLeapYearFeb29 + }) + void testBasicIsoDate_InvalidDate_butMatches(String date) { + // 虽然日期有误,但这个正则无法判断。实际工作中,应使用日期时间 API。 + Matcher matcher = PatternConsts.BASIC_ISO_DATE.matcher(date); + assertTrue(matcher.matches()); + } + + @ParameterizedTest + @ValueSource(strings = { + "2023041", // TooShort + "99999999990415", // TooLong + "2023-04-15", // NonNumeric + }) + void testBasicIsoDate_InvalidDate_Mismatches(String date) { + Matcher matcher = PatternConsts.BASIC_ISO_DATE.matcher(date); + assertFalse(matcher.matches()); + } + + // ================================ + // #endregion - BASIC_ISO_DATE + // ================================ + + // ================================ + // #region - ISO_LOCAL_DATE + // ================================ + + @Test + void testIsoLocalDate_ValidDate() { + Matcher matcher = PatternConsts.ISO_LOCAL_DATE.matcher("2024-12-29"); + assertTrue(matcher.matches()); + assertEquals("2024", matcher.group("yyyy")); + assertEquals("12", matcher.group("MM")); + assertEquals("29", matcher.group("dd")); + + // LeapYearFeb29() + assertTrue(PatternConsts.ISO_LOCAL_DATE.matcher("2020-02-29").matches()); + + // BoundaryMin() + assertTrue(PatternConsts.ISO_LOCAL_DATE.matcher("0000-01-01").matches()); + + // BoundaryMax() + assertTrue(PatternConsts.ISO_LOCAL_DATE.matcher("999999999-12-31").matches()); + } + + @ParameterizedTest + @ValueSource(strings = { + "2023-13-01", // InvalidMonth + "2023-02-30", // InvalidDay + "2021-02-29", // NonLeapYearFeb29 + }) + void testIsoLocalDate_InvalidDate_butMatches(String date) { + // 虽然日期有误,但这个正则无法判断。实际工作中,应使用日期时间 API。 + Matcher matcher = PatternConsts.ISO_LOCAL_DATE.matcher(date); + assertTrue(matcher.matches()); + } + + @ParameterizedTest + @ValueSource(strings = { + "2023-04-1", // TooShort + "9999999999-04-15", // TooLong + "20230415", + }) + void testIsoLocalDate_InvalidDate_Mismatches(String date) { + Matcher matcher = PatternConsts.ISO_LOCAL_DATE.matcher(date); + assertFalse(matcher.matches()); + } + + // ================================ + // #endregion - ISO_LOCAL_DATE + // ================================ + + // ================================ + // #region - PASSWORD + // ================================ + + @Test + void testPassword_ValidPassword_Matches() { + assertTrue(PatternConsts.PASSWORD.matcher("Abc123!@#").matches()); + } + + @Test + void testPassword_InvalidPassword_Mismatches() { + assertFalse(PatternConsts.PASSWORD.matcher("Abc123 !@#").matches()); // 带空格 + assertFalse(PatternConsts.PASSWORD.matcher("Abc123!@# ").matches()); // 带空格 + assertFalse(PatternConsts.PASSWORD.matcher(" Abc123!@#").matches()); // 带空格 + assertFalse(PatternConsts.PASSWORD.matcher(" Abc123!@# ").matches()); // 带空格 + assertFalse(PatternConsts.PASSWORD.matcher("77553366998844113322").matches()); // 纯数字 + assertFalse(PatternConsts.PASSWORD.matcher("poiujhgbfdsazxcfvghj").matches()); // 纯小写字母 + assertFalse(PatternConsts.PASSWORD.matcher("POIUJHGBFDSAZXCFVGHJ").matches()); // 纯大写字母 + assertFalse(PatternConsts.PASSWORD.matcher("!#$%&'*\\+-/=?^`{|}~@()[]\",.;':").matches()); // 纯特殊字符 + assertFalse(PatternConsts.PASSWORD.matcher("sdfrghbv525842582752").matches()); // 没有小写字母 + assertFalse(PatternConsts.PASSWORD.matcher("SDFRGHBV525842582752").matches()); // 没有小写字母 + assertFalse(PatternConsts.PASSWORD.matcher("sdfrghbvSDFRGHBV").matches()); // 没有数字 + assertFalse(PatternConsts.PASSWORD.matcher("Abc1!").matches()); // 太短 + assertFalse(PatternConsts.PASSWORD.matcher("Abc1!Abc1!Abc1!Abc1!Abc1!Abc1!Abc1!").matches()); // 太长 + assertFalse(PatternConsts.PASSWORD.matcher("").matches()); + assertFalse(PatternConsts.PASSWORD.matcher(" ").matches()); + } + + // ================================ + // #endregion - PASSWORD + // ================================ + + // ================================ + // #region - EMAIL + // ================================ + + @Test + public void testValidEmails() { + assertTrue(PatternConsts.EMAIL.matcher("test@example.com").matches()); + assertTrue(PatternConsts.EMAIL.matcher("user.name+tag+sorting@example.com").matches()); + assertTrue(PatternConsts.EMAIL.matcher("user@sub.example.com").matches()); + assertTrue(PatternConsts.EMAIL.matcher("user@123.123.123.123").matches()); + } + + @Test + public void testInvalidEmails() { + assertFalse(PatternConsts.EMAIL.matcher(".username@example.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("@missingusername.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("plainaddress").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username..username@example.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username.@example.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@-example.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@-example.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@.com.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@.com.my").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@.com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@com.").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@example..com").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@example.com-").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@example.com.").matches()); + assertFalse(PatternConsts.EMAIL.matcher("username@example").matches()); + } + + // ================================ + // #endregion - EMAIL + // ================================ + + // ================================ + // #region - Chinese2ndIdCardNumber + // ================================ + + @ParameterizedTest + @ValueSource(strings = { + "44520019900101456X", + "44520019900101456x", + "445200199001014566", + }) + void testChinese2ndIdCardNumber_ValidChinese2ndIdCardNumber(String value) { + Matcher matcher = PatternConsts.CHINESE_2ND_ID_CARD_NUMBER.matcher(value); + assertTrue(matcher.matches()); + assertEquals("44", matcher.group("province")); + assertEquals("4452", matcher.group("city")); + assertEquals("445200", matcher.group("county")); + } + + @ParameterizedTest + @ValueSource(strings = { + "4452200199001014566", + "44520199001014566", + " ", + "", + }) + void testChinese2ndIdCardNumber_InvalidChinese2ndIdCardNumber(String value) { + assertFalse(PatternConsts.CHINESE_2ND_ID_CARD_NUMBER.matcher(value).matches()); + } + + // ================================ + // #endregion - Chinese2ndIdCardNumber + // ================================ +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/exception/test/InvalidInputExceptionTests.java b/src/test/java/xyz/zhouxy/plusone/commons/exception/test/InvalidInputExceptionTests.java new file mode 100644 index 0000000..79a2bfc --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/exception/test/InvalidInputExceptionTests.java @@ -0,0 +1,288 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.exception.test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import lombok.extern.slf4j.Slf4j; +import xyz.zhouxy.plusone.commons.exception.business.InvalidInputException; + +@Slf4j +public class InvalidInputExceptionTests { + + // ================================ + // #region - createByType + // ================================ + + @Test + void test_createByType() { + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.create(); + }); + assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS, e.getType()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.getCode(), e.getCode()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.getDefaultMessage(), e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withMessage() { + final String message = "test_createByType_withMessage"; + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message); + }); + assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withNullMessage() { + final String message = null; + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.PICTURE_CONTAINS_ILLEGAL_INFORMATION.create(message); + }); + assertSame(InvalidInputException.Type.PICTURE_CONTAINS_ILLEGAL_INFORMATION, e.getType()); + assertEquals(InvalidInputException.Type.PICTURE_CONTAINS_ILLEGAL_INFORMATION.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withCause() { + Arrays.asList("test_createByType_withCause", null).forEach(message -> { + + NumberFormatException nfe = new NumberFormatException(message); + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.INFRINGE_COPYRIGHT.create(nfe); + }); + + assertSame(InvalidInputException.Type.INFRINGE_COPYRIGHT, e.getType()); + assertEquals(InvalidInputException.Type.INFRINGE_COPYRIGHT.getCode(), e.getCode()); + log.info("{}", e.getMessage()); + assertEquals(nfe.toString(), e.getMessage()); + assertSame(nfe, e.getCause()); + }); + } + + @Test + void test_createByType_withNullCause() { + NumberFormatException nfe = null; + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.DEFAULT.create(nfe); + }); + + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withMessageAndCause() { + final String message = "test_createByType_withMessageAndCause"; + final NumberFormatException nfe = new NumberFormatException("NumberFormatExceptionMessage"); + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.create(message, nfe); + }); + assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS, e.getType()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_AND_MALICIOUS_LINKS.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertSame(nfe, e.getCause()); + } + + @Test + void test_createByType_withNullMessageAndCause() { + final String message = null; + final NullPointerException nfe = new NullPointerException("Context is null."); + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message, nfe); + }); + assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertSame(nfe, e.getCause()); + } + + @Test + void test_createByType_withMessageAndNullCause() { + final String message = "test_createByType_withMessageAndNullCause"; + final NullPointerException npe = null; + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message, npe); + }); + assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withNullMessageAndNullCause() { + final String message = null; + final NullPointerException nfe = null; + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.create(message, nfe); + }); + assertSame(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS, e.getType()); + assertEquals(InvalidInputException.Type.CONTAINS_ILLEGAL_WORDS.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + // ================================ + // #endregion - createByType + // ================================ + + // ================================ + // #region - constructor + // ================================ + + @Test + void testConstructor() { + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertEquals(InvalidInputException.Type.DEFAULT.getDefaultMessage(), e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testConstructor_withMessage() { + final String message = "testConstructor_withMessage"; + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(message); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testConstructor_withNullMessage() { + final String message = null; + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(message); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testConstructor_withCause() { + Arrays.asList("testConstructor_withCause", null).forEach(message -> { + + NumberFormatException nfe = new NumberFormatException(message); + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(nfe); + }); + + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + log.info("{}", e.getMessage()); + assertEquals(nfe.toString(), e.getMessage()); + assertSame(nfe, e.getCause()); + }); + } + + @Test + void testConstructor_withNullCause() { + NumberFormatException nfe = null; + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(nfe); + }); + + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testConstructor_withMessageAndCause() { + final String message = "testConstructor_withMessageAndCause"; + final NumberFormatException nfe = new NumberFormatException("NumberFormatExceptionMessage"); + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(message, nfe); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertSame(nfe, e.getCause()); + } + + @Test + void testConstructor_withNullMessageAndCause() { + final String message = null; + final NullPointerException nfe = new NullPointerException("Context is null."); + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(message, nfe); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertSame(nfe, e.getCause()); + } + + @Test + void testConstructor_withMessageAndNullCause() { + final String message = "testConstructor_withMessageAndNullCause"; + final NullPointerException npe = null; + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(message, npe); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testConstructor_withNullMessageAndNullCause() { + final String message = null; + final NullPointerException nfe = null; + + InvalidInputException e = assertThrows(InvalidInputException.class, () -> { + throw new InvalidInputException(message, nfe); + }); + assertSame(InvalidInputException.Type.DEFAULT, e.getType()); + assertEquals(InvalidInputException.Type.DEFAULT.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + // ================================ + // #endregion - constructor + // ================================ +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/exception/test/ParsingFailureExceptionTests.java b/src/test/java/xyz/zhouxy/plusone/commons/exception/test/ParsingFailureExceptionTests.java new file mode 100644 index 0000000..13ecd13 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/exception/test/ParsingFailureExceptionTests.java @@ -0,0 +1,367 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.exception.test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.Arrays; + +import org.junit.jupiter.api.Test; + +import lombok.extern.slf4j.Slf4j; +import xyz.zhouxy.plusone.commons.exception.ParsingFailureException; + +@Slf4j +public class ParsingFailureExceptionTests { + + // ================================ + // #region - createByType + // ================================ + + @Test + void test_createByType() { + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(); + }); + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getDefaultMessage(), e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withMessage() { + final String message = "test_createByType_withMessage"; + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.JSON_PARSING_FAILURE.create(message); + }); + assertSame(ParsingFailureException.Type.JSON_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.Type.JSON_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withNullMessage() { + final String message = null; + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.XML_PARSING_FAILURE.create(message); + }); + assertSame(ParsingFailureException.XML_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.XML_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withCause() { + Arrays.asList("test_createByType_withCause", null).forEach(message -> { + + NumberFormatException nfe = new NumberFormatException(message); + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.NUMBER_PARSING_FAILURE.create(nfe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + log.info("{}", e.getMessage()); + assertEquals(nfe.toString(), e.getMessage()); + assertSame(nfe, e.getCause()); + }); + } + + @Test + void test_createByType_withNullCause() { + NumberFormatException nfe = null; + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.NUMBER_PARSING_FAILURE.create(nfe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withMessageAndCause() { + final String message = "test_createByType_withMessageAndCause"; + final NumberFormatException nfe = new NumberFormatException("NumberFormatExceptionMessage"); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.NUMBER_PARSING_FAILURE.create(message, nfe); + }); + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertSame(nfe, e.getCause()); + } + + @Test + void test_createByType_withNullMessageAndCause() { + final String message = null; + final NullPointerException nfe = new NullPointerException("Context is null."); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(message, nfe); + }); + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertSame(nfe, e.getCause()); + } + + @Test + void test_createByType_withMessageAndNullCause() { + final String message = "test_createByType_withMessageAndNullCause"; + final NullPointerException npe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(message, npe); + }); + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByType_withNullMessageAndNullCause() { + final String message = null; + final NullPointerException nfe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.DATE_TIME_PARSING_FAILURE.create(message, nfe); + }); + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + // ================================ + // #endregion - createByType + // ================================ + + // ================================ + // #region - of DateTimeParseException + // ================================ + + @Test + void test_createByOf_withDateTimeParseException() { + DateTimeParseException dtpe = assertThrows(DateTimeParseException.class, () -> { + LocalDateTime.parse("abcd", DateTimeFormatter.ISO_DATE_TIME); + }); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(dtpe); + }); + + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(dtpe.getMessage(), e.getMessage()); + assertSame(dtpe, e.getCause()); + } + + @Test + void test_createByOf_withNullDateTimeParseException() { + DateTimeParseException dtpe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(dtpe); + }); + + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getDefaultMessage(), e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByOf_withDateTimeParseExceptionAndMessage() { + final String message = "test_createByOf_withDateTimeParseExceptionAndMessage"; + DateTimeParseException dtpe = assertThrows(DateTimeParseException.class, () -> { + LocalDateTime.parse("abcd", DateTimeFormatter.ISO_DATE_TIME); + }); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertSame(dtpe, e.getCause()); + } + + @Test + void test_createByOf_withDateTimeParseExceptionAndNullMessage() { + final String message = null; + DateTimeParseException dtpe = assertThrows(DateTimeParseException.class, () -> { + LocalDateTime.parse("abcd", DateTimeFormatter.ISO_DATE_TIME); + }); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertSame(dtpe, e.getCause()); + } + + @Test + void test_createByOf_withNullDateTimeParseExceptionAndMessage() { + final String message = "test_createByOf_withDateTimeParseExceptionAndMessage"; + DateTimeParseException dtpe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByOf_withNullDateTimeParseExceptionAndNullMessage() { + final String message = null; + DateTimeParseException dtpe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.DATE_TIME_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.DATE_TIME_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + // ================================ + // #endregion - of DateTimeParseException + // ================================ + + // ================================ + // #region - of NumberFormatException + // ================================ + + @Test + void test_createByOf_withNumberFormatException() { + NumberFormatException dtpe = assertThrows(NumberFormatException.class, () -> { + Integer.parseInt("abcd"); + }); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(dtpe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(dtpe.getMessage(), e.getMessage()); + assertSame(dtpe, e.getCause()); + } + + @Test + void test_createByOf_withNullNumberFormatException() { + NumberFormatException dtpe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(dtpe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getDefaultMessage(), e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByOf_withNumberFormatExceptionAndMessage() { + final String message = "test_createByOf_withNumberFormatExceptionAndMessage"; + NumberFormatException dtpe = assertThrows(NumberFormatException.class, () -> { + Integer.parseInt("abcd"); + }); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertSame(dtpe, e.getCause()); + } + + @Test + void test_createByOf_withNumberFormatExceptionAndNullMessage() { + final String message = null; + NumberFormatException dtpe = assertThrows(NumberFormatException.class, () -> { + Integer.parseInt("abcd"); + }); + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertSame(dtpe, e.getCause()); + } + + @Test + void test_createByOf_withNullNumberFormatExceptionAndMessage() { + final String message = "test_createByOf_withNumberFormatExceptionAndMessage"; + NumberFormatException dtpe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void test_createByOf_withNullNumberFormatExceptionAndNullMessage() { + final String message = null; + NumberFormatException dtpe = null; + + ParsingFailureException e = assertThrows(ParsingFailureException.class, () -> { + throw ParsingFailureException.of(message, dtpe); + }); + + assertSame(ParsingFailureException.NUMBER_PARSING_FAILURE, e.getType()); + assertEquals(ParsingFailureException.NUMBER_PARSING_FAILURE.getCode(), e.getCode()); + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + // ================================ + // #endregion - of NumberFormatException + // ================================ + +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumberTests.java b/src/test/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumberTests.java index 7002115..07421fe 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumberTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/model/Chinese2ndGenIDCardNumberTests.java @@ -17,13 +17,15 @@ package xyz.zhouxy.plusone.commons.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.time.LocalDate; import java.time.format.DateTimeParseException; -import java.util.regex.Matcher; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import lombok.extern.slf4j.Slf4j; @@ -31,20 +33,11 @@ import lombok.extern.slf4j.Slf4j; public class Chinese2ndGenIDCardNumberTests { @Test - void testPattern() { - Matcher matcher = Chinese2ndGenIDCardNumber.PATTERN.matcher("11010520000101111X"); - assertTrue(matcher.matches()); - for (int i = 0; i < matcher.groupCount(); i++) { - log.info("{}: {}", i, matcher.group(i)); - } - } - - @Test - void test() throws CloneNotSupportedException { + void testOf_success() { Chinese2ndGenIDCardNumber idCardNumber = Chinese2ndGenIDCardNumber.of("11010520000101111X"); assertEquals("11010520000101111X", idCardNumber.value()); assertEquals(LocalDate.of(2000, 1, 1), idCardNumber.getBirthDate()); - assertEquals(Gender.MALE, idCardNumber.getGender()); + assertSame(Gender.MALE, idCardNumber.getGender()); assertEquals("110105", idCardNumber.getCountyCode()); assertEquals("110105000000", idCardNumber.getFullCountyCode()); @@ -56,18 +49,43 @@ public class Chinese2ndGenIDCardNumberTests { assertEquals("北京", idCardNumber.getProvinceName()); - assertThrows(IllegalArgumentException.class, - () -> Chinese2ndGenIDCardNumber.of("1101520000101111")); + assertEquals("1***************1X", idCardNumber.toDesensitizedString()); + assertEquals("110***********111X", idCardNumber.toDesensitizedString(3, 4)); + assertEquals("110###############", idCardNumber.toDesensitizedString('#', 3, 0)); + assertEquals("11010520000101111X", idCardNumber.toDesensitizedString(10, 8)); - assertThrows(IllegalArgumentException.class, - () -> Chinese2ndGenIDCardNumber.of("11010520002101111X")); + assertThrows(IllegalArgumentException.class, () -> idCardNumber.toDesensitizedString(-1, 5)); + assertThrows(IllegalArgumentException.class, () -> idCardNumber.toDesensitizedString(5, -1)); + assertThrows(IllegalArgumentException.class, () -> idCardNumber.toDesensitizedString(10, 9)); + } - try { - Chinese2ndGenIDCardNumber.of("11010520002101111X"); - } - catch (IllegalArgumentException e) { - log.error(e.getMessage(), e); - assertTrue(e.getCause() instanceof DateTimeParseException); + @Test + void testOf_blankValue() { + String[] strings = { null, "", " " }; + for (String value : strings) { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Chinese2ndGenIDCardNumber.of(value)); + assertEquals("二代居民身份证校验失败:号码为空", e.getMessage()); } } + @ParameterizedTest + @ValueSource(strings = { "1101520000101111", "110A0520000101111X", "110105220000101111X" }) + void testOf_mismatched(String value) { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Chinese2ndGenIDCardNumber.of(value)); + assertEquals("二代居民身份证校验失败:" + value, e.getMessage()); + } + + @Test + void testOf_wrongBirthDate() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> Chinese2ndGenIDCardNumber.of("11010520002101111X")); + assertTrue(e.getCause() instanceof DateTimeParseException); + } + + @Test + void testOf_wrongProvince() { + assertThrows(IllegalArgumentException.class, + () -> Chinese2ndGenIDCardNumber.of("99010520000101111X")); + } } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java b/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java index 6fd5209..be3eb22 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/model/ValidatableStringRecordTests.java @@ -84,7 +84,7 @@ class User { } @ValueObject -class Email extends ValidatableStringRecord { +class Email extends ValidatableStringRecord { private Email(String value) { super(value, PatternConsts.EMAIL); } @@ -96,7 +96,7 @@ class Email extends ValidatableStringRecord { } @ValueObject -class Username extends ValidatableStringRecord { +class Username extends ValidatableStringRecord { private Username(String username) { super(username, PatternConsts.USERNAME); } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponseTests.java b/src/test/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponseTests.java new file mode 100644 index 0000000..45a6ed2 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/model/dto/UnifiedResponseTests.java @@ -0,0 +1,679 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.model.dto; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import com.google.gson.Gson; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import xyz.zhouxy.plusone.commons.exception.business.BizException; +import xyz.zhouxy.plusone.commons.model.dto.UnifiedResponse.SuccessResult; + +@Slf4j +public +class UnifiedResponseTests { + + static final ObjectMapper jackson = new ObjectMapper(); + + static final Gson gson = new Gson(); + + static final PageResult pageResult = PageResult.of(Lists.newArrayList( + new User("zhouxy1", "zhouxy1@gmail.com"), + new User("zhouxy2", "zhouxy2@gmail.com") + ), 108); + + static { + jackson.setSerializationInclusion(Include.NON_NULL); + } + + @Test + void testSuccess_WithoutArgument() throws Exception { + // 1. success without argument + UnifiedResponse success = UnifiedResponse.success(); + assertEquals(SuccessResult.SUCCESS_STATUS, success.getStatus()); + assertEquals("SUCCESS", success.getMessage()); + assertNull(success.getData()); + String jacksonSuccess = jackson.writeValueAsString(success); + log.info("jacksonSuccess: {}", jacksonSuccess); + assertEquals("{\"status\":\"2000000\",\"message\":\"SUCCESS\"}", jacksonSuccess); + } + + @Test + void testSuccess_WithMessage() throws Exception { + // 2. success with message + UnifiedResponse successWithMessage = UnifiedResponse.success("成功"); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithMessage.getStatus()); + assertEquals("成功", successWithMessage.getMessage()); + assertNull(successWithMessage.getData()); + String jacksonSuccessWithMessage = jackson.writeValueAsString(successWithMessage); + log.info("jacksonSuccessWithMessage: {}", jacksonSuccessWithMessage); + assertEquals("{\"status\":\"2000000\",\"message\":\"成功\"}", jacksonSuccessWithMessage); + } + + @Test + void testSuccess_WithMessageAndNullData() throws Exception { + // success with message and null data + final UnifiedResponse successWithMessageAndNullData = UnifiedResponse.success("查询成功", null); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithMessageAndNullData.getStatus()); + assertEquals("查询成功", successWithMessageAndNullData.getMessage()); + assertNull(successWithMessageAndNullData.getData()); + final String jacksonSuccessWithMessageAndNullData = jackson.writeValueAsString(successWithMessageAndNullData); + log.info("jacksonSuccessWithMessageAndNullData: {}", jacksonSuccessWithMessageAndNullData); + assertEquals("{\"status\":\"2000000\",\"message\":\"查询成功\"}", jacksonSuccessWithMessageAndNullData); + + assertEquals("{status: \"2000000\", message: \"查询成功\", data: null}", successWithMessageAndNullData.toString()); + } + + @Test + void testSuccess_WithMessageAndStringData() throws Exception { + UnifiedResponse successWithStringData = UnifiedResponse.success("查询成功", "zhouxy"); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithStringData.getStatus()); + assertEquals("查询成功", successWithStringData.getMessage()); + assertEquals("zhouxy", successWithStringData.getData()); + String jacksonSuccessWithStringData = jackson.writeValueAsString(successWithStringData); + log.info("jacksonSuccessWithStringData: {}", jacksonSuccessWithStringData); + assertEquals("{\"status\":\"2000000\",\"message\":\"查询成功\",\"data\":\"zhouxy\"}", jacksonSuccessWithStringData); + + assertEquals("{status: \"2000000\", message: \"查询成功\", data: \"zhouxy\"}", successWithStringData.toString()); + } + + @Test + void testSuccess_WithMessageAndIntegerData() throws Exception { + final UnifiedResponse successWithIntegerData = UnifiedResponse.success("查询成功", 1); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithIntegerData.getStatus()); + assertEquals("查询成功", successWithIntegerData.getMessage()); + assertEquals(1, successWithIntegerData.getData()); + final String jacksonSuccessWithIntegerData = jackson.writeValueAsString(successWithIntegerData); + log.info("jacksonSuccessWithIntegerData: {}", jacksonSuccessWithIntegerData); + assertEquals("{\"status\":\"2000000\",\"message\":\"查询成功\",\"data\":1}", jacksonSuccessWithIntegerData); + + assertEquals("{status: \"2000000\", message: \"查询成功\", data: 1}", successWithIntegerData.toString()); + } + + @Test + void testSuccess_WithMessageAndData() throws Exception { + UnifiedResponse successWithData = UnifiedResponse.success("查询成功", pageResult); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithData.getStatus()); + assertEquals("查询成功", successWithData.getMessage()); + assertNotNull(successWithData.getData()); + assertEquals(pageResult, successWithData.getData()); + String jacksonSuccessWithData = jackson.writeValueAsString(successWithData); + log.info("jacksonSuccessWithData: {}", jacksonSuccessWithData); + assertEquals("{\"status\":\"2000000\",\"message\":\"查询成功\",\"data\":{\"total\":108,\"content\":[{\"username\":\"zhouxy1\",\"email\":\"zhouxy1@gmail.com\"},{\"username\":\"zhouxy2\",\"email\":\"zhouxy2@gmail.com\"}]}}", jacksonSuccessWithData); + } + + @Test + void testSuccess_WithNullMessage() throws Exception { + // 3. success with null message + UnifiedResponse successWithNullMessage = UnifiedResponse.success(null); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithNullMessage.getStatus()); + assertEquals("", successWithNullMessage.getMessage()); + assertNull(successWithNullMessage.getData()); + String jacksonSuccessWithNullMessage = jackson.writeValueAsString(successWithNullMessage); + log.info("jacksonSuccessWithNullMessage: {}", jacksonSuccessWithNullMessage); + assertEquals("{\"status\":\"2000000\",\"message\":\"\"}", jacksonSuccessWithNullMessage); + } + + // success with null message and null data + @Test + void testSuccess_WithNullMessageAndNullData() throws Exception { + final UnifiedResponse successWithNullMessageAndNullData = UnifiedResponse.success(null, null); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithNullMessageAndNullData.getStatus()); + assertEquals("", successWithNullMessageAndNullData.getMessage()); + assertNull(successWithNullMessageAndNullData.getData()); + + final String jacksonSuccessWithNullMessageAndNullData = jackson.writeValueAsString(successWithNullMessageAndNullData); + log.info("jacksonSuccessWithNullMessageAndNullData: {}", jacksonSuccessWithNullMessageAndNullData); + assertEquals("{\"status\":\"2000000\",\"message\":\"\"}", jacksonSuccessWithNullMessageAndNullData); + + assertEquals("{status: \"2000000\", message: \"\", data: null}", successWithNullMessageAndNullData.toString()); + } + + @Test + void testSuccess_WithNullMessageAndData() throws Exception { + // success with null message and data + final User user = new User("zhouxy", "zhouxy@code108.cn"); + final UnifiedResponse successWithNullMessageAndData = UnifiedResponse.success(null, user); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithNullMessageAndData.getStatus()); + assertEquals("", successWithNullMessageAndData.getMessage()); + assertEquals(user, successWithNullMessageAndData.getData()); + final String jacksonSuccessWithNullMessageAndData = jackson.writeValueAsString(successWithNullMessageAndData); + log.info("jacksonSuccessWithNullMessageAndData: {}", jacksonSuccessWithNullMessageAndData); + assertEquals("{\"status\":\"2000000\",\"message\":\"\",\"data\":{\"username\":\"zhouxy\",\"email\":\"zhouxy@code108.cn\"}}", + jacksonSuccessWithNullMessageAndData); + } + + @Test + void testSuccess_WithEmptyMessage() throws Exception { + // 4. success with empty message + UnifiedResponse successWithEmptyMessage = UnifiedResponse.success(""); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithEmptyMessage.getStatus()); + assertEquals("", successWithEmptyMessage.getMessage()); + assertNull(successWithEmptyMessage.getData()); + String jacksonSuccessWithEmptyMessage = jackson.writeValueAsString(successWithEmptyMessage); + log.info("jacksonSuccessWithEmptyMessage: {}", jacksonSuccessWithEmptyMessage); + assertEquals("{\"status\":\"2000000\",\"message\":\"\"}", jacksonSuccessWithEmptyMessage); + } + + // success with empty message and null data + @Test + void testSuccess_WithEmptyMessageAndNullData() throws Exception { + final UnifiedResponse successWithEmptyMessageAndNullData = UnifiedResponse.success("", null); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithEmptyMessageAndNullData.getStatus()); + assertEquals("", successWithEmptyMessageAndNullData.getMessage()); + assertNull(successWithEmptyMessageAndNullData.getData()); + + final String jacksonSuccessWithEmptyMessageAndNullData = jackson.writeValueAsString(successWithEmptyMessageAndNullData); + log.info("jacksonSuccessWithEmptyMessageAndNullData: {}", jacksonSuccessWithEmptyMessageAndNullData); + assertEquals("{\"status\":\"2000000\",\"message\":\"\"}", jacksonSuccessWithEmptyMessageAndNullData); + + assertEquals("{status: \"2000000\", message: \"\", data: null}", successWithEmptyMessageAndNullData.toString()); + } + + // success with empty message and data + @Test + void testSuccess_WithEmptyMessageAndData() throws Exception { + final User user = new User("zhouxy", "zhouxy@gmail.com"); + final UnifiedResponse successWithEmptyMessageAndData = UnifiedResponse.success("", user); + assertEquals(SuccessResult.SUCCESS_STATUS, successWithEmptyMessageAndData.getStatus()); + assertEquals("", successWithEmptyMessageAndData.getMessage()); + assertEquals(user, successWithEmptyMessageAndData.getData()); + + final String jacksonSuccessWithEmptyMessageAndData = jackson.writeValueAsString(successWithEmptyMessageAndData); + log.info("jacksonSuccessWithEmptyMessageAndData: {}", jacksonSuccessWithEmptyMessageAndData); + assertEquals("{\"status\":\"2000000\",\"message\":\"\",\"data\":{\"username\":\"zhouxy\",\"email\":\"zhouxy@gmail.com\"}}", jacksonSuccessWithEmptyMessageAndData); + } + + @Test + void testError_WithMessage() throws Exception { + UnifiedResponse errorWithMessage = UnifiedResponse.error("查询失败"); + assertEquals("9999999", errorWithMessage.getStatus()); + assertEquals("查询失败", errorWithMessage.getMessage()); + assertNull(errorWithMessage.getData()); + + final String jacksonErrorWithMessage = jackson.writeValueAsString(errorWithMessage); + assertEquals("{\"status\":\"9999999\",\"message\":\"查询失败\"}", jacksonErrorWithMessage); + final String gsonErrorWithMessage = gson.toJson(errorWithMessage); + assertEquals("{\"status\":\"9999999\",\"message\":\"查询失败\"}", gsonErrorWithMessage); + } + + @Test + void testError_WithMessage_AndNullData() throws Exception { + UnifiedResponse errorWithMessageAndNullData = UnifiedResponse.error("查询失败", (Object) null); + assertEquals("9999999", errorWithMessageAndNullData.getStatus()); + assertEquals("查询失败", errorWithMessageAndNullData.getMessage()); + assertNull(errorWithMessageAndNullData.getData()); + + final String jacksonErrorWithMessageAndNullData = jackson.writeValueAsString(errorWithMessageAndNullData); + assertEquals("{\"status\":\"9999999\",\"message\":\"查询失败\"}", jacksonErrorWithMessageAndNullData); + final String gsonErrorWithMessageAndNullData = gson.toJson(errorWithMessageAndNullData); + assertEquals("{\"status\":\"9999999\",\"message\":\"查询失败\"}", gsonErrorWithMessageAndNullData); + } + + @Test + void testError_WithMessage_AndData() throws Exception { + final User user = new User("zhouxy", "zhouxy@gmail.com"); + UnifiedResponse errorWithMessageAndData = UnifiedResponse.error("查询失败", user); + assertEquals("9999999", errorWithMessageAndData.getStatus()); + assertEquals("查询失败", errorWithMessageAndData.getMessage()); + assertEquals(user, errorWithMessageAndData.getData()); + + final String jacksonErrorWithMessageAndData = jackson.writeValueAsString(errorWithMessageAndData); + assertEquals("{\"status\":\"9999999\",\"message\":\"查询失败\",\"data\":{\"username\":\"zhouxy\",\"email\":\"zhouxy@gmail.com\"}}", + jacksonErrorWithMessageAndData); + final String gsonErrorWithMessageAndData = gson.toJson(errorWithMessageAndData); + assertEquals("{\"status\":\"9999999\",\"message\":\"查询失败\",\"data\":{\"username\":\"zhouxy\",\"email\":\"zhouxy@gmail.com\"}}", + gsonErrorWithMessageAndData); + } + + @Test + void testError_WithNullMessage() throws Exception { + UnifiedResponse errorWithNullMessage = UnifiedResponse.error((String) null); + assertEquals("9999999", errorWithNullMessage.getStatus()); + assertEquals("", errorWithNullMessage.getMessage()); + assertNull(errorWithNullMessage.getData()); + + final String jacksonErrorWithNullMessage = jackson.writeValueAsString(errorWithNullMessage); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", jacksonErrorWithNullMessage); + final String gsonErrorWithNullMessage = gson.toJson(errorWithNullMessage); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", gsonErrorWithNullMessage); + } + + @Test + void testError_WithNullMessage_AndNullData() throws Exception { + UnifiedResponse errorWithNullMessageAndNullData = UnifiedResponse.error((String) null, (User) null); + assertEquals("9999999", errorWithNullMessageAndNullData.getStatus()); + assertEquals("", errorWithNullMessageAndNullData.getMessage()); + assertNull(errorWithNullMessageAndNullData.getData()); + + final String jacksonErrorWithNullMessageAndNullData = jackson.writeValueAsString(errorWithNullMessageAndNullData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", jacksonErrorWithNullMessageAndNullData); + final String gsonErrorWithNullMessageAndNullData = gson.toJson(errorWithNullMessageAndNullData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", gsonErrorWithNullMessageAndNullData); + } + + @Test + void testError_WithNullMessage_AndData() throws Exception { + final User user = new User("zhouxy1", "zhouxy1@gmail.com"); + UnifiedResponse errorWithNullMessageAndData = UnifiedResponse.error((String) null, user); + assertEquals("9999999", errorWithNullMessageAndData.getStatus()); + assertEquals("", errorWithNullMessageAndData.getMessage()); + assertEquals(user, errorWithNullMessageAndData.getData()); + + final String jacksonErrorWithNullMessageAndData = jackson.writeValueAsString(errorWithNullMessageAndData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\",\"data\":{\"username\":\"zhouxy1\",\"email\":\"zhouxy1@gmail.com\"}}", + jacksonErrorWithNullMessageAndData); + final String gsonErrorWithNullMessageAndData = gson.toJson(errorWithNullMessageAndData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\",\"data\":{\"username\":\"zhouxy1\",\"email\":\"zhouxy1@gmail.com\"}}", + gsonErrorWithNullMessageAndData); + } + + @Test + void testError_WithEmptyMessage() throws Exception { + UnifiedResponse errorWithEmptyMessage = UnifiedResponse.error(""); + assertEquals("9999999", errorWithEmptyMessage.getStatus()); + assertEquals("", errorWithEmptyMessage.getMessage()); + assertNull(errorWithEmptyMessage.getData()); + final String jacksonErrorWithEmptyMessage = jackson.writeValueAsString(errorWithEmptyMessage); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", jacksonErrorWithEmptyMessage); + final String gsonErrorWithEmptyMessage = gson.toJson(errorWithEmptyMessage); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", gsonErrorWithEmptyMessage); + } + + @Test + void testError_WithEmptyMessage_AndNullData() throws Exception { + UnifiedResponse errorWithEmptyMessageAndNullData = UnifiedResponse.error("", (User) null); + assertEquals("9999999", errorWithEmptyMessageAndNullData.getStatus()); + assertEquals("", errorWithEmptyMessageAndNullData.getMessage()); + assertNull(errorWithEmptyMessageAndNullData.getData()); + + final String jacksonErrorEmptyNullMessageAndNullData = jackson.writeValueAsString(errorWithEmptyMessageAndNullData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", jacksonErrorEmptyNullMessageAndNullData); + final String gsonErrorWithEmptyMessageAndNullData = gson.toJson(errorWithEmptyMessageAndNullData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\"}", gsonErrorWithEmptyMessageAndNullData); + } + + @Test + void testError_WithEmptyMessage_AndData() throws Exception { + final User user = new User("zhouxy1", "zhouxy1@gmail.com"); + UnifiedResponse errorWithEmptyMessageAndData = UnifiedResponse.error("", user); + assertEquals("9999999", errorWithEmptyMessageAndData.getStatus()); + assertEquals("", errorWithEmptyMessageAndData.getMessage()); + assertEquals(user, errorWithEmptyMessageAndData.getData()); + + final String jacksonErrorWithEmptyMessageAndData = jackson.writeValueAsString(errorWithEmptyMessageAndData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\",\"data\":{\"username\":\"zhouxy1\",\"email\":\"zhouxy1@gmail.com\"}}", + jacksonErrorWithEmptyMessageAndData); + final String gsonErrorWithEmptyMessageAndData = gson.toJson(errorWithEmptyMessageAndData); + assertEquals("{\"status\":\"9999999\",\"message\":\"\",\"data\":{\"username\":\"zhouxy1\",\"email\":\"zhouxy1@gmail.com\"}}", + gsonErrorWithEmptyMessageAndData); + } + + @Test + void testError_WithStatusAndMessage() throws Exception { + final UnifiedResponse errorWithStatusAndMessage = UnifiedResponse.error(108, "查询失败"); + assertEquals(108, errorWithStatusAndMessage.getStatus()); + assertEquals("查询失败", errorWithStatusAndMessage.getMessage()); + assertNull(errorWithStatusAndMessage.getData()); + assertEquals("{status: 108, message: \"查询失败\", data: null}", errorWithStatusAndMessage.toString()); + + final String jacksonErrorWithStatusAndMessage = jackson.writeValueAsString(errorWithStatusAndMessage); + log.info("jacksonErrorWithStatusAndMessage: {}", jacksonErrorWithStatusAndMessage); + assertEquals("{\"status\":108,\"message\":\"查询失败\"}", jacksonErrorWithStatusAndMessage); + + final String gsonErrorWithStatusAndMessage = gson.toJson(errorWithStatusAndMessage); + assertEquals("{\"status\":108,\"message\":\"查询失败\"}", gsonErrorWithStatusAndMessage); + } + + @Test + void testError_WithStatusAndMessage_AndNullData() throws Exception { + final UnifiedResponse errorWithStatusAndMessageAndNullData = UnifiedResponse.error(108, "查询失败", null); + assertEquals(108, errorWithStatusAndMessageAndNullData.getStatus()); + assertEquals("查询失败", errorWithStatusAndMessageAndNullData.getMessage()); + assertNull(errorWithStatusAndMessageAndNullData.getData()); + assertEquals("{status: 108, message: \"查询失败\", data: null}", errorWithStatusAndMessageAndNullData.toString()); + + final String jacksonErrorWithStatusAndMessageAndNullData = jackson.writeValueAsString(errorWithStatusAndMessageAndNullData); + log.info("jacksonErrorWithStatusAndMessage: {}", jacksonErrorWithStatusAndMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"查询失败\"}", jacksonErrorWithStatusAndMessageAndNullData); + + final String gsonErrorWithStatusAndMessageAndNullData = gson.toJson(errorWithStatusAndMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"查询失败\"}", gsonErrorWithStatusAndMessageAndNullData); + } + + @Test + void testError_WithStatusAndMessage_AndData() throws Exception { + final PageResult emptyPageResult = PageResult.empty(); + final UnifiedResponse errorWithStatusAndMessageAndData = UnifiedResponse.error(108, "查询失败", emptyPageResult); + assertEquals(108, errorWithStatusAndMessageAndData.getStatus()); + assertEquals("查询失败", errorWithStatusAndMessageAndData.getMessage()); + assertEquals(emptyPageResult, errorWithStatusAndMessageAndData.getData()); + assertEquals("{status: 108, message: \"查询失败\", data: PageResult [total=0, content=[]]}", errorWithStatusAndMessageAndData.toString()); + + final String jacksonErrorWithStatusAndMessageAndData = jackson.writeValueAsString(errorWithStatusAndMessageAndData); + assertEquals("{\"status\":108,\"message\":\"查询失败\",\"data\":{\"total\":0,\"content\":[]}}", + jacksonErrorWithStatusAndMessageAndData); + + final String gsonErrorWithStatusAndMessageAndData = gson.toJson(errorWithStatusAndMessageAndData); + assertEquals("{\"status\":108,\"message\":\"查询失败\",\"data\":{\"total\":0,\"content\":[]}}", + gsonErrorWithStatusAndMessageAndData); + } + + @Test + void testError_WithStatusAndNullMessage() throws Exception { + UnifiedResponse errorWithStatusAndNullMessage = UnifiedResponse.error(500, (String) null); + assertEquals(500, errorWithStatusAndNullMessage.getStatus()); + assertEquals("", errorWithStatusAndNullMessage.getMessage()); + assertNull(errorWithStatusAndNullMessage.getData()); + + final String jacksonErrorWithStatusAndNullMessage = jackson.writeValueAsString(errorWithStatusAndNullMessage); + assertEquals("{\"status\":500,\"message\":\"\"}", jacksonErrorWithStatusAndNullMessage); + + final String gsonErrorWithStatusAndNullMessage = gson.toJson(errorWithStatusAndNullMessage); + assertEquals("{\"status\":500,\"message\":\"\"}", gsonErrorWithStatusAndNullMessage); + } + + @Test + void testError_WithStatusAndNullMessage_AndNullData() throws Exception { + UnifiedResponse errorWithStatusAndNullMessageAndNullData = UnifiedResponse.error(500, (String) null, null); + + assertEquals(500, errorWithStatusAndNullMessageAndNullData.getStatus()); + assertEquals("", errorWithStatusAndNullMessageAndNullData.getMessage()); + assertNull(errorWithStatusAndNullMessageAndNullData.getData()); + + final String jacksonErrorWithStatusAndNullMessageAndNullData = jackson.writeValueAsString(errorWithStatusAndNullMessageAndNullData); + assertEquals("{\"status\":500,\"message\":\"\"}", jacksonErrorWithStatusAndNullMessageAndNullData); + + final String gsonErrorWithStatusAndNullMessageAndNullData = gson.toJson(errorWithStatusAndNullMessageAndNullData); + assertEquals("{\"status\":500,\"message\":\"\"}", gsonErrorWithStatusAndNullMessageAndNullData); + } + + @Test + void testError_WithStatusAndNullMessage_AndData() throws Exception { + PageResult emptyPageResult = PageResult.empty(); + UnifiedResponse errorWithStatusAndNullMessageAndData = UnifiedResponse.error(500, (String) null, emptyPageResult); + assertEquals(500, errorWithStatusAndNullMessageAndData.getStatus()); + assertEquals("", errorWithStatusAndNullMessageAndData.getMessage()); + assertEquals(emptyPageResult, errorWithStatusAndNullMessageAndData.getData()); + final String jacksonErrorWithStatusAndNullMessageAndData = jackson.writeValueAsString(errorWithStatusAndNullMessageAndData); + assertEquals("{\"status\":500,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", jacksonErrorWithStatusAndNullMessageAndData); + final String gsonErrorWithStatusAndNullMessageAndData = gson.toJson(errorWithStatusAndNullMessageAndData); + assertEquals("{\"status\":500,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", gsonErrorWithStatusAndNullMessageAndData); + } + + @Test + void testError_WithStatusAndEmptyMessage() throws Exception { + UnifiedResponse errorWithStatusAndEmptyMessage = UnifiedResponse.error(500, ""); + assertEquals(500, errorWithStatusAndEmptyMessage.getStatus()); + assertEquals("", errorWithStatusAndEmptyMessage.getMessage()); + assertNull(errorWithStatusAndEmptyMessage.getData()); + + final String jacksonErrorWithStatusAndEmptyMessage = jackson.writeValueAsString(errorWithStatusAndEmptyMessage); + assertEquals("{\"status\":500,\"message\":\"\"}", jacksonErrorWithStatusAndEmptyMessage); + + final String gsonErrorWithStatusAndEmptyMessage = gson.toJson(errorWithStatusAndEmptyMessage); + assertEquals("{\"status\":500,\"message\":\"\"}", gsonErrorWithStatusAndEmptyMessage); + } + + @Test + void testError_WithStatusAndEmptyMessage_AndNullData() throws Exception { + UnifiedResponse errorWithStatusAndEmptyMessageAndNullData = UnifiedResponse.error(500, "", null); + + assertEquals(500, errorWithStatusAndEmptyMessageAndNullData.getStatus()); + assertEquals("", errorWithStatusAndEmptyMessageAndNullData.getMessage()); + assertNull(errorWithStatusAndEmptyMessageAndNullData.getData()); + + final String jacksonErrorWithStatusAndEmptyMessageAndNullData = jackson.writeValueAsString(errorWithStatusAndEmptyMessageAndNullData); + assertEquals("{\"status\":500,\"message\":\"\"}", jacksonErrorWithStatusAndEmptyMessageAndNullData); + + final String gsonErrorWithStatusAndEmptyMessageAndNullData = gson.toJson(errorWithStatusAndEmptyMessageAndNullData); + assertEquals("{\"status\":500,\"message\":\"\"}", gsonErrorWithStatusAndEmptyMessageAndNullData); + } + + @Test + void testError_WithStatusAndEmptyMessage_AndData() throws Exception { + PageResult emptyPageResult = PageResult.empty(); + UnifiedResponse errorWithStatusAndEmptyMessageAndData = UnifiedResponse.error(500, "", emptyPageResult); + assertEquals(500, errorWithStatusAndEmptyMessageAndData.getStatus()); + assertEquals("", errorWithStatusAndEmptyMessageAndData.getMessage()); + assertEquals(emptyPageResult, errorWithStatusAndEmptyMessageAndData.getData()); + final String jacksonErrorWithStatusAndEmptyMessageAndData = jackson.writeValueAsString(errorWithStatusAndEmptyMessageAndData); + assertEquals("{\"status\":500,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", jacksonErrorWithStatusAndEmptyMessageAndData); + final String gsonErrorWithStatusAndEmptyMessageAndData = gson.toJson(errorWithStatusAndEmptyMessageAndData); + assertEquals("{\"status\":500,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", gsonErrorWithStatusAndEmptyMessageAndData); + } + + @Test + void testError_WithStatusAndThrowable() throws Exception { + final IllegalArgumentException e = new IllegalArgumentException("ID cannot be null"); + final UnifiedResponse errorWithStatusThrowable = UnifiedResponse.error(500, e); + assertEquals(500, errorWithStatusThrowable.getStatus()); + assertEquals("ID cannot be null", errorWithStatusThrowable.getMessage()); + assertNull(errorWithStatusThrowable.getData()); + assertEquals("{\"status\":500,\"message\":\"ID cannot be null\"}", jackson.writeValueAsString(errorWithStatusThrowable)); + assertEquals("{\"status\":500,\"message\":\"ID cannot be null\"}", gson.toJson(errorWithStatusThrowable)); + } + + @Test + void testError_WithStatusAndNullThrowable() { + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(500, (Throwable) null)); + } + + @Test + void testError_WithNullStatus() { + final Object nullStatus = null; + final String nullMessage = null; + final User user = new User("zhouxy", "zhouxy@gmail.com"); + + // message + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "查询失败")); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "查询失败", null)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "查询失败", user)); + + // empty message + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "")); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "", null)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "", user)); + + // null message + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, nullMessage)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "查询失败", null)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, "查询失败", user)); + + // Throwable + BizException bizException = new BizException("业务异常"); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, bizException)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.error(nullStatus, (Throwable) null)); + } + + + @Test + void testOf_WithStatusAndMessage() throws Exception { + final UnifiedResponse ofWithStatusAndMessage = UnifiedResponse.of(108, "This is a message."); + assertEquals(108, ofWithStatusAndMessage.getStatus()); + assertEquals("This is a message.", ofWithStatusAndMessage.getMessage()); + assertNull(ofWithStatusAndMessage.getData()); + + final String jacksonOfWithStatusAndMessage = jackson.writeValueAsString(ofWithStatusAndMessage); + log.info("jacksonOfWithStatusAndMessage: {}", jacksonOfWithStatusAndMessage); + assertEquals("{\"status\":108,\"message\":\"This is a message.\"}", jacksonOfWithStatusAndMessage); + + assertEquals("{status: 108, message: \"This is a message.\", data: null}", ofWithStatusAndMessage.toString()); + + final String gsonOfWithStatusAndMessage = gson.toJson(ofWithStatusAndMessage); + assertEquals("{\"status\":108,\"message\":\"This is a message.\"}", gsonOfWithStatusAndMessage); + } + + @Test + void testOf_WithStatusAndMessage_AndNullData() throws Exception { + final UnifiedResponse ofWithStatusAndMessageAndNullData = UnifiedResponse.of(108, "This is a message.", null); + assertEquals(108, ofWithStatusAndMessageAndNullData.getStatus()); + assertEquals("This is a message.", ofWithStatusAndMessageAndNullData.getMessage()); + assertNull(ofWithStatusAndMessageAndNullData.getData()); + + final String jacksonOfWithStatusAndMessageAndNullData = jackson.writeValueAsString(ofWithStatusAndMessageAndNullData); + log.info("jacksonOfWithStatusAndMessage: {}", jacksonOfWithStatusAndMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"This is a message.\"}", jacksonOfWithStatusAndMessageAndNullData); + + assertEquals("{status: 108, message: \"This is a message.\", data: null}", ofWithStatusAndMessageAndNullData.toString()); + + final String gsonOfWithStatusAndMessageAndNullData = gson.toJson(ofWithStatusAndMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"This is a message.\"}", gsonOfWithStatusAndMessageAndNullData); + } + + @Test + void testOf_WithStatusAndMessage_AndData() throws Exception { + final PageResult emptyPageResult = PageResult.empty(); + final UnifiedResponse ofWithStatusAndMessageAndData + = UnifiedResponse.of(108, "This is a message.", emptyPageResult); + assertEquals("{status: 108, message: \"This is a message.\", data: PageResult [total=0, content=[]]}", + ofWithStatusAndMessageAndData.toString()); + assertEquals(108, ofWithStatusAndMessageAndData.getStatus()); + assertEquals("This is a message.", ofWithStatusAndMessageAndData.getMessage()); + assertEquals(emptyPageResult, ofWithStatusAndMessageAndData.getData()); + final String jacksonOfWithStatusAndMessageAndData = jackson.writeValueAsString(ofWithStatusAndMessageAndData); + assertEquals("{\"status\":108,\"message\":\"This is a message.\",\"data\":{\"total\":0,\"content\":[]}}", + jacksonOfWithStatusAndMessageAndData); + final String gsonOfWithStatusAndMessageAndData = gson.toJson(ofWithStatusAndMessageAndData); + assertEquals("{\"status\":108,\"message\":\"This is a message.\",\"data\":{\"total\":0,\"content\":[]}}", + gsonOfWithStatusAndMessageAndData); + } + + @Test + void testOf_WithStatusAndNullMessage() throws Exception { + UnifiedResponse ofWithStatusAndNullMessage = UnifiedResponse.of(108, (String) null); + assertEquals(108, ofWithStatusAndNullMessage.getStatus()); + assertEquals("", ofWithStatusAndNullMessage.getMessage()); + assertNull(ofWithStatusAndNullMessage.getData()); + + final String jacksonOfWithStatusAndNullMessage = jackson.writeValueAsString(ofWithStatusAndNullMessage); + assertEquals("{\"status\":108,\"message\":\"\"}", jacksonOfWithStatusAndNullMessage); + + final String gsonOfWithStatusAndNullMessage = gson.toJson(ofWithStatusAndNullMessage); + assertEquals("{\"status\":108,\"message\":\"\"}", gsonOfWithStatusAndNullMessage); + } + + @Test + void testOf_WithStatusAndNullMessage_AndNullData() throws Exception { + UnifiedResponse ofWithStatusAndNullMessageAndNullData = UnifiedResponse.of(108, (String) null, null); + + assertEquals(108, ofWithStatusAndNullMessageAndNullData.getStatus()); + assertEquals("", ofWithStatusAndNullMessageAndNullData.getMessage()); + assertNull(ofWithStatusAndNullMessageAndNullData.getData()); + + final String jacksonOfWithStatusAndNullMessageAndNullData = jackson.writeValueAsString(ofWithStatusAndNullMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"\"}", jacksonOfWithStatusAndNullMessageAndNullData); + + final String gsonOfWithStatusAndNullMessageAndNullData = gson.toJson(ofWithStatusAndNullMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"\"}", gsonOfWithStatusAndNullMessageAndNullData); + } + + @Test + void testOf_WithStatusAndNullMessage_AndData() throws Exception { + PageResult emptyPageResult = PageResult.empty(); + UnifiedResponse ofWithStatusAndNullMessageAndData = UnifiedResponse.of(108, (String) null, emptyPageResult); + assertEquals(108, ofWithStatusAndNullMessageAndData.getStatus()); + assertEquals("", ofWithStatusAndNullMessageAndData.getMessage()); + assertEquals(emptyPageResult, ofWithStatusAndNullMessageAndData.getData()); + final String jacksonOfWithStatusAndNullMessageAndData = jackson.writeValueAsString(ofWithStatusAndNullMessageAndData); + assertEquals("{\"status\":108,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", jacksonOfWithStatusAndNullMessageAndData); + final String gsonOfWithStatusAndNullMessageAndData = gson.toJson(ofWithStatusAndNullMessageAndData); + assertEquals("{\"status\":108,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", gsonOfWithStatusAndNullMessageAndData); + } + + @Test + void testOf_WithStatusAndEmptyMessage() throws Exception { + UnifiedResponse ofWithStatusAndEmptyMessage = UnifiedResponse.of(108, ""); + assertEquals(108, ofWithStatusAndEmptyMessage.getStatus()); + assertEquals("", ofWithStatusAndEmptyMessage.getMessage()); + assertNull(ofWithStatusAndEmptyMessage.getData()); + + final String jacksonOfWithStatusAndEmptyMessage = jackson.writeValueAsString(ofWithStatusAndEmptyMessage); + assertEquals("{\"status\":108,\"message\":\"\"}", jacksonOfWithStatusAndEmptyMessage); + + final String gsonOfWithStatusAndEmptyMessage = gson.toJson(ofWithStatusAndEmptyMessage); + assertEquals("{\"status\":108,\"message\":\"\"}", gsonOfWithStatusAndEmptyMessage); + } + + @Test + void testOf_WithStatusAndEmptyMessage_AndNullData() throws Exception { + UnifiedResponse ofWithStatusAndEmptyMessageAndNullData = UnifiedResponse.of(108, "", null); + + assertEquals(108, ofWithStatusAndEmptyMessageAndNullData.getStatus()); + assertEquals("", ofWithStatusAndEmptyMessageAndNullData.getMessage()); + assertNull(ofWithStatusAndEmptyMessageAndNullData.getData()); + + final String jacksonOfWithStatusAndEmptyMessageAndNullData = jackson.writeValueAsString(ofWithStatusAndEmptyMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"\"}", jacksonOfWithStatusAndEmptyMessageAndNullData); + + final String gsonOfWithStatusAndEmptyMessageAndNullData = gson.toJson(ofWithStatusAndEmptyMessageAndNullData); + assertEquals("{\"status\":108,\"message\":\"\"}", gsonOfWithStatusAndEmptyMessageAndNullData); + } + + @Test + void testOf_WithStatusAndEmptyMessage_AndData() throws Exception { + PageResult emptyPageResult = PageResult.empty(); + UnifiedResponse ofWithStatusAndEmptyMessageAndData = UnifiedResponse.of(108, "", emptyPageResult); + assertEquals(108, ofWithStatusAndEmptyMessageAndData.getStatus()); + assertEquals("", ofWithStatusAndEmptyMessageAndData.getMessage()); + assertEquals(emptyPageResult, ofWithStatusAndEmptyMessageAndData.getData()); + final String jacksonOfWithStatusAndEmptyMessageAndData = jackson.writeValueAsString(ofWithStatusAndEmptyMessageAndData); + assertEquals("{\"status\":108,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", jacksonOfWithStatusAndEmptyMessageAndData); + final String gsonOfWithStatusAndEmptyMessageAndData = gson.toJson(ofWithStatusAndEmptyMessageAndData); + assertEquals("{\"status\":108,\"message\":\"\",\"data\":{\"total\":0,\"content\":[]}}", gsonOfWithStatusAndEmptyMessageAndData); + } + + @Test + void testOf_WithNullStatus() { + final Object nullStatus = null; + final String nullMessage = null; + final User user = new User("zhouxy", "zhouxy@gmail.com"); + + // message + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "查询失败")); + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "查询失败", null)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "查询失败", user)); + + // empty message + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "")); + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "", null)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "", user)); + + // null message + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, nullMessage)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "查询失败", null)); + assertThrows(NullPointerException.class, () -> UnifiedResponse.of(nullStatus, "查询失败", user)); + } + + @Data + @NoArgsConstructor + @AllArgsConstructor + private static class User { + private String username; + private String email; + } + +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/model/dto/test/PagingAndSortingQueryParamsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/model/dto/test/PagingAndSortingQueryParamsTests.java new file mode 100644 index 0000000..2810acc --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/model/dto/test/PagingAndSortingQueryParamsTests.java @@ -0,0 +1,288 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.model.dto.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.Statement; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; + +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.h2.jdbcx.JdbcDataSource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import xyz.zhouxy.plusone.commons.model.dto.PageResult; +import xyz.zhouxy.plusone.commons.model.dto.PagingAndSortingQueryParams; +import xyz.zhouxy.plusone.commons.model.dto.PagingParams; + +@Slf4j +public class PagingAndSortingQueryParamsTests { + + static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + static void setUp() throws Exception { + initDatabase(); + + String resource = "mybatis-config.xml"; + InputStream inputStream = Resources.getResourceAsStream(resource); + sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); + } + + static void initDatabase() throws Exception { + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL("jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE;MODE=MySQL"); + dataSource.setUser("sa"); + dataSource.setPassword(""); + + List data = Lists.newArrayList( + new AccountVO(1L, "zhouxy01", "zhouxy01@qq.com", 0, 108L, LocalDateTime.of(2020, 1, 1, 13, 15), 0L), + new AccountVO(2L, "zhouxy02", "zhouxy02@qq.com", 0, 108L, LocalDateTime.of(2020, 1, 2, 13, 15), 0L), + new AccountVO(3L, "zhouxy03", "zhouxy03@qq.com", 0, 108L, LocalDateTime.of(2020, 1, 3, 13, 15), 0L), + new AccountVO(4L, "zhouxy04", "zhouxy04@qq.com", 0, 108L, LocalDateTime.of(2020, 1, 4, 13, 15), 0L), + new AccountVO(5L, "zhouxy05", "zhouxy05@qq.com", 0, 108L, LocalDateTime.of(2020, 1, 5, 13, 15), 0L), + new AccountVO(6L, "zhouxy06", "zhouxy06@qq.com", 0, 108L, LocalDateTime.of(2024, 1, 6, 13, 15), 0L), + new AccountVO(7L, "zhouxy07", "zhouxy07@qq.com", 0, 108L, LocalDateTime.of(2024, 1, 7, 13, 15), 0L), + new AccountVO(8L, "zhouxy08", "zhouxy08@qq.com", 1, 108L, LocalDateTime.of(2024, 5, 8, 13, 15), 0L), + new AccountVO(9L, "zhouxy09", "zhouxy09@qq.com", 1, 108L, LocalDateTime.of(2024, 5, 9, 13, 15), 0L), + new AccountVO(10L, "zhouxy10", "zhouxy10@qq.com", 1, 108L, LocalDateTime.of(2024, 5, 10, 13, 15), 0L), + new AccountVO(11L, "zhouxy11", "zhouxy11@qq.com", 1, 108L, LocalDateTime.of(2024, 5, 11, 13, 15), 0L), + new AccountVO(12L, "zhouxy12", "zhouxy12@qq.com", 1, 108L, LocalDateTime.of(2024, 5, 12, 13, 15), 0L), + new AccountVO(13L, "zhouxy13", "zhouxy13@qq.com", 1, 108L, LocalDateTime.of(2024, 5, 13, 13, 15), 0L), + new AccountVO(14L, "zhouxy14", "zhouxy14@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 14, 13, 15), 0L), + new AccountVO(15L, "zhouxy15", "zhouxy15@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 15, 13, 15), 0L), + new AccountVO(16L, "zhouxy16", "zhouxy16@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 16, 13, 15), 0L), + new AccountVO(17L, "zhouxy17", "zhouxy17@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 17, 13, 15), 0L), + new AccountVO(18L, "zhouxy18", "zhouxy18@qq.com", 1, 108L, LocalDateTime.of(2024, 10, 18, 13, 15), 0L), + new AccountVO(19L, "zhouxy19", "zhouxy19@qq.com", 1, 108L, LocalDateTime.of(2024, 11, 19, 13, 15), 0L), + new AccountVO(20L, "zhouxy20", "zhouxy20@qq.com", 1, 108L, LocalDateTime.of(2024, 12, 20, 13, 15), 0L) + ); + + try (Connection conn = dataSource.getConnection()) { + try (Statement statement = conn.createStatement()) { + String ddl = "CREATE TABLE sys_account (" + + "\n" + " id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY" + + "\n" + " ,username VARCHAR(255) NOT NULL" + + "\n" + " ,email VARCHAR(255) NOT NULL" + + "\n" + " ,status VARCHAR(2) NOT NULL" + + "\n" + " ,create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP" + + "\n" + " ,created_by BIGINT NOT NULL" + + "\n" + " ,version BIGINT NOT NULL DEFAULT 0" + + "\n" + ")"; + statement.execute(ddl); + } + + String sql = "INSERT INTO sys_account(id, username, email, status, create_time, created_by, version) VALUES" + + "\n" + "(?, ?, ?, ?, ?, ?, ?)"; + try (PreparedStatement statement = conn.prepareStatement(sql)) { + for (AccountVO a : data) { + statement.setObject(1, a.getId()); + statement.setObject(2, a.getUsername()); + statement.setObject(3, a.getEmail()); + statement.setObject(4, a.getStatus()); + statement.setObject(5, a.getCreateTime()); + statement.setObject(6, a.getCreatedBy()); + statement.setObject(7, a.getVersion()); + statement.addBatch(); + } + statement.executeBatch(); + statement.clearBatch(); + } + } + } + + static final String JSON_STR = "" + + "{\n" + + " \"pageNum\": 3,\n" + + " \"size\": 3,\n" + + " \"orderBy\": [\"username-asc\"],\n" + + " \"createTimeStart\": \"2024-05-06\",\n" + + " \"createTimeEnd\": \"2030-07-06\"" + + "}"; + + static final String WRONG_JSON_STR = "" + + "{\n" + + " \"pageNum\": 3,\n" + + " \"size\": 3,\n" + + " \"orderBy\": [\"status-asc\"],\n" + + " \"createTimeStart\": \"2024-05-06\",\n" + + " \"createTimeEnd\": \"2030-07-06\"" + + "}"; + + @Test + void testJackson() throws Exception { + ObjectMapper jackson = new ObjectMapper(); + jackson.registerModule(new JavaTimeModule()); + try (SqlSession session = sqlSessionFactory.openSession()) { + AccountQueryParams params = jackson.readValue(JSON_STR, AccountQueryParams.class); + PagingParams pagingParams = params.buildPagingParams(); + + AccountQueries accountQueries = session.getMapper(AccountQueries.class); + List list = accountQueries.queryAccountList(params, pagingParams); + long count = accountQueries.countAccount(params); + PageResult accountPageResult = PageResult.of(list, count); + log.info(jackson.writeValueAsString(accountPageResult)); + + assertEquals(Lists.newArrayList( + new AccountVO(14L, "zhouxy14", "zhouxy14@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 14, 13, 15), 0L), + new AccountVO(15L, "zhouxy15", "zhouxy15@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 15, 13, 15), 0L), + new AccountVO(16L, "zhouxy16", "zhouxy16@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 16, 13, 15), 0L) + ), accountPageResult.getContent()); + assertEquals(13, accountPageResult.getTotal()); + } catch (Exception e) { + log.error("测试不通过", e); + throw e; + } + + AccountQueryParams queryParams = jackson.readValue(WRONG_JSON_STR, AccountQueryParams.class); + assertThrows(IllegalArgumentException.class, () -> queryParams.buildPagingParams()); // NOSONAR + } + + static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + @Test + void testGson() throws Exception { + Gson gson = new GsonBuilder() + .registerTypeAdapter(LocalDate.class, new TypeAdapter() { + + @Override + public void write(JsonWriter out, LocalDate value) throws IOException { + out.value(dateFormatter.format(value)); + } + + @Override + public LocalDate read(JsonReader in) throws IOException { + return LocalDate.parse(in.nextString(), dateFormatter); + } + + }) + .create(); + try (SqlSession session = sqlSessionFactory.openSession()) { + AccountQueryParams params = gson.fromJson(JSON_STR, AccountQueryParams.class); + log.info(params.toString()); + PagingParams pagingParams = params.buildPagingParams(); + log.info("pagingParams: {}", pagingParams); + AccountQueries accountQueries = session.getMapper(AccountQueries.class); + List list = accountQueries.queryAccountList(params, pagingParams); + long count = accountQueries.countAccount(params); + PageResult accountPageResult = PageResult.of(list, count); + log.info(gson.toJson(accountPageResult)); + + assertEquals(Lists.newArrayList( + new AccountVO(14L, "zhouxy14", "zhouxy14@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 14, 13, 15), 0L), + new AccountVO(15L, "zhouxy15", "zhouxy15@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 15, 13, 15), 0L), + new AccountVO(16L, "zhouxy16", "zhouxy16@qq.com", 1, 108L, LocalDateTime.of(2024, 8, 16, 13, 15), 0L) + ), accountPageResult.getContent()); + assertEquals(13, accountPageResult.getTotal()); + } catch (Exception e) { + log.error("测试不通过", e); + throw e; + } + + AccountQueryParams queryParams = gson.fromJson(WRONG_JSON_STR, AccountQueryParams.class); + assertThrows(IllegalArgumentException.class, () -> queryParams.buildPagingParams()); // NOSONAR + } +} + + +/** + * 账号信息查询参数 + * + * @author ZhouXY + */ +@ToString(callSuper = true) +class AccountQueryParams extends PagingAndSortingQueryParams { + + private static final Map PROPERTY_COLUMN_MAP = ImmutableMap.builder() + .put("id", "id") + .put("username", "username") + .put("createTime", "create_time") + .build(); + + public AccountQueryParams() { + super(PROPERTY_COLUMN_MAP); + } + + private @Getter @Setter Long id; + private @Getter @Setter String username; + private @Getter @Setter String email; + private @Getter @Setter Integer status; + private @Getter @Setter Long createdBy; + private @Getter @Setter LocalDate createTimeStart; + private @Setter LocalDate createTimeEnd; + + public LocalDate getCreateTimeEnd() { + if (this.createTimeEnd == null) { + return null; + } + return this.createTimeEnd.plusDays(1); + } +} + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +class AccountVO { + private Long id; + private String username; + private String email; + private Integer status; + private Long createdBy; + private LocalDateTime createTime; + private Long version; +} + +interface AccountQueries { + + List queryAccountList(@Param("query") AccountQueryParams query, + @Param("page") PagingParams page); + + long countAccount(@Param("query") AccountQueryParams query); +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/queryparams/test/PagingAndSortingQueryParamsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/queryparams/test/PagingAndSortingQueryParamsTests.java deleted file mode 100644 index d6241bc..0000000 --- a/src/test/java/xyz/zhouxy/plusone/commons/queryparams/test/PagingAndSortingQueryParamsTests.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.queryparams.test; - -import java.io.IOException; -import java.time.LocalDate; -import java.time.format.DateTimeFormatter; -import java.util.Map; - -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.google.common.collect.ImmutableMap; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonWriter; - -import lombok.Getter; -import lombok.Setter; -import lombok.ToString; -import lombok.extern.slf4j.Slf4j; -import xyz.zhouxy.plusone.commons.model.dto.PagingAndSortingQueryParams; -import xyz.zhouxy.plusone.commons.model.dto.PagingParams; - -@Slf4j -public // -class PagingAndSortingQueryParamsTests { - static final String JSON_STR = "" + - "{\n" + - " \"size\": 15,\n" + - " \"orderBy\": [\"username-asc\"],\n" + - " \"createTimeStart\": \"2024-05-06\",\n" + - " \"createTimeEnd\": \"2024-07-06\",\n" + - " \"updateTimeStart\": \"2024-08-06\",\n" + - " \"updateTimeEnd\": \"2024-10-06\",\n" + - " \"mobilePhone\": \"13169053215\"\n" + - "}"; - - @Test - void testJackson() throws Exception { - try { - ObjectMapper om = new ObjectMapper(); - om.registerModule(new JavaTimeModule()); - AccountQueryParams params = om.readValue(JSON_STR, AccountQueryParams.class); - log.info(params.toString()); - PagingParams pagingParams = params.buildPagingParams(); - log.info("pagingParams: {}", pagingParams); - } catch (Exception e) { - log.error("测试不通过", e); - throw e; - } - } - - static final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); - - static final TypeAdapter dateAdapter = new TypeAdapter() { - - @Override - public void write(JsonWriter out, LocalDate value) throws IOException { - out.value(dateFormatter.format(value)); - } - - @Override - public LocalDate read(JsonReader in) throws IOException { - return LocalDate.parse(in.nextString(), dateFormatter); - } - - }; - - @Test - void testGson() throws Exception { - try { - Gson gson = new GsonBuilder() - .registerTypeAdapter(LocalDate.class, dateAdapter) - .create(); - AccountQueryParams params = gson.fromJson(JSON_STR, AccountQueryParams.class); - log.info(params.toString()); - PagingParams pagingParams = params.buildPagingParams(); - log.info("pagingParams: {}", pagingParams); - } catch (Exception e) { - log.error("测试不通过", e); - throw e; - } - } -} - - -/** - * 账号信息查询参数 - * - * @author ZhouXY - */ -@ToString(callSuper = true) -class AccountQueryParams extends PagingAndSortingQueryParams { - - private static final Map PROPERTY_COLUMN_MAP = ImmutableMap.builder() - .put("id", "id") - .put("username", "username") - .put("email", "email") - .put("mobilePhone", "mobile_phone") - .put("status", "status") - .put("nickname", "nickname") - .put("sex", "sex") - .put("createdBy", "created_by") - .put("createTime", "create_time") - .put("updatedBy", "updated_by") - .put("updateTime", "update_time") - .build(); - - public AccountQueryParams() { - super(PROPERTY_COLUMN_MAP); - } - - private @Getter @Setter Long id; - private @Getter @Setter String username; - private @Getter @Setter String email; - private @Getter @Setter String mobilePhone; - private @Getter @Setter Integer status; - private @Getter @Setter String nickname; - private @Getter @Setter Integer sex; - private @Getter @Setter Long createdBy; - private @Getter @Setter LocalDate createTimeStart; - private @Setter LocalDate createTimeEnd; - private @Getter @Setter Long updatedBy; - private @Getter @Setter LocalDate updateTimeStart; - private @Setter LocalDate updateTimeEnd; - private @Getter @Setter Long roleId; - - public LocalDate getCreateTimeEnd() { - if (this.createTimeEnd == null) { - return null; - } - return this.createTimeEnd.plusDays(1); - } - - public LocalDate getUpdateTimeEnd() { - if (this.updateTimeEnd == null) { - return null; - } - return this.updateTimeEnd.plusDays(1); - } -} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/time/QuarterTests.java b/src/test/java/xyz/zhouxy/plusone/commons/time/QuarterTests.java index 69637a8..976ccc7 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/time/QuarterTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/time/QuarterTests.java @@ -18,6 +18,7 @@ package xyz.zhouxy.plusone.commons.time; import static org.junit.jupiter.api.Assertions.*; +import java.time.DateTimeException; import java.time.Month; import java.time.MonthDay; @@ -37,7 +38,7 @@ class QuarterTests { assertEquals(1, quarter.getValue()); assertEquals("Q1", quarter.name()); - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(DateTimeException.class, () -> { Quarter.of(0); }); @@ -84,7 +85,7 @@ class QuarterTests { assertEquals(2, quarter.getValue()); assertEquals("Q2", quarter.name()); - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(DateTimeException.class, () -> { Quarter.of(5); }); @@ -240,4 +241,116 @@ class QuarterTests { firstDayOfYear = Quarter.Q4.firstDayOfYear(false); assertEquals(1 + (31 + 28 + 31) + (30 + 31 + 30) + (31 + 30 + 31), firstDayOfYear); } + + @Test + void testPlusZeroAndPositiveRealNumbers() { + for (int i = 0; i < 100; i += 4) { + assertEquals(Quarter.Q1, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q2, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q3, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q4, Quarter.Q4.plus(i)); + } + for (int i = 1; i < 100 + 1; i += 4) { + assertEquals(Quarter.Q2, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q3, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q4, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q1, Quarter.Q4.plus(i)); + } + for (int i = 2; i < 100 + 2; i += 4) { + assertEquals(Quarter.Q3, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q4, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q1, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q2, Quarter.Q4.plus(i)); + } + for (int i = 3; i < 100 + 3; i += 4) { + assertEquals(Quarter.Q4, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q1, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q2, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q3, Quarter.Q4.plus(i)); + } + } + + @Test + void testPlusZeroAndNegativeNumber() { + for (int i = 0; i > -100; i -= 4) { + assertEquals(Quarter.Q1, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q2, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q3, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q4, Quarter.Q4.plus(i)); + } + for (int i = -1; i > -(100 + 1); i -= 4) { + assertEquals(Quarter.Q4, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q1, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q2, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q3, Quarter.Q4.plus(i)); + } + for (int i = -2; i > -(100 + 2); i -= 4) { + assertEquals(Quarter.Q3, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q4, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q1, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q2, Quarter.Q4.plus(i)); + } + for (int i = -3; i > -(100 + 3); i -= 4) { + assertEquals(Quarter.Q2, Quarter.Q1.plus(i)); + assertEquals(Quarter.Q3, Quarter.Q2.plus(i)); + assertEquals(Quarter.Q4, Quarter.Q3.plus(i)); + assertEquals(Quarter.Q1, Quarter.Q4.plus(i)); + } + } + + @Test + void testMinusZeroAndNegativeNumber() { + for (int i = 0; i < 100; i += 4) { + assertEquals(Quarter.Q1, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q2, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q3, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q4, Quarter.Q4.minus(i)); + } + for (int i = 1; i < 100 + 1; i += 4) { + assertEquals(Quarter.Q4, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q1, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q2, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q3, Quarter.Q4.minus(i)); + } + for (int i = 2; i < 100 + 2; i += 4) { + assertEquals(Quarter.Q3, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q4, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q1, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q2, Quarter.Q4.minus(i)); + } + for (int i = 3; i < 100 + 3; i += 4) { + assertEquals(Quarter.Q2, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q3, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q4, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q1, Quarter.Q4.minus(i)); + } + } + + @Test + void testMinusZeroAndPositiveRealNumbers() { + for (int i = 0; i > -100; i -= 4) { + assertEquals(Quarter.Q1, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q2, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q3, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q4, Quarter.Q4.minus(i)); + } + for (int i = -1; i > -(100 + 1); i -= 4) { + assertEquals(Quarter.Q2, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q3, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q4, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q1, Quarter.Q4.minus(i)); + } + for (int i = -2; i > -(100 + 2); i -= 4) { + assertEquals(Quarter.Q3, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q4, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q1, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q2, Quarter.Q4.minus(i)); + } + for (int i = -3; i > -(100 + 3); i -= 4) { + assertEquals(Quarter.Q4, Quarter.Q1.minus(i)); + assertEquals(Quarter.Q1, Quarter.Q2.minus(i)); + assertEquals(Quarter.Q2, Quarter.Q3.minus(i)); + assertEquals(Quarter.Q3, Quarter.Q4.minus(i)); + } + } } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/time/YearQuarterTests.java b/src/test/java/xyz/zhouxy/plusone/commons/time/YearQuarterTests.java index 0583839..fcb14a6 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/time/YearQuarterTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/time/YearQuarterTests.java @@ -21,66 +21,775 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import lombok.extern.slf4j.Slf4j; +import java.time.DateTimeException; import java.time.LocalDate; +import java.time.Month; +import java.time.Year; import java.time.YearMonth; - +import java.util.Calendar; +import java.util.Date; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @Slf4j public class YearQuarterTests { - @Test - void of_ValidYearAndQuarter_CreatesYearQuarter() { - int year = 2023; - Quarter quarter = Quarter.Q1; + // ================================ + // #region - of(int year, int quarter) + // ================================ - YearQuarter expected = YearQuarter.of(year, quarter); - YearQuarter actual = YearQuarter.of(LocalDate.of(year, 2, 28)); + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3, 4 }) + void of_ValidYearAndQuarterValue_CreatesYearQuarter(int quarter) { + { + int year = 2024; + YearQuarter yearQuarter = YearQuarter.of(year, quarter); + log.info("{}", yearQuarter); + assertEquals(year, yearQuarter.getYear()); + assertEquals(Quarter.of(quarter), yearQuarter.getQuarter()); + assertEquals(quarter, yearQuarter.getQuarterValue()); + } + { + int year = Year.MIN_VALUE; + YearQuarter yearQuarter = YearQuarter.of(year, quarter); + assertEquals(year, yearQuarter.getYear()); + assertEquals(Quarter.of(quarter), yearQuarter.getQuarter()); + assertEquals(quarter, yearQuarter.getQuarterValue()); + } + { + int year = Year.MAX_VALUE; + YearQuarter yearQuarter = YearQuarter.of(year, quarter); + assertEquals(year, yearQuarter.getYear()); + assertEquals(Quarter.of(quarter), yearQuarter.getQuarter()); + assertEquals(quarter, yearQuarter.getQuarterValue()); + } + } - assertEquals(expected, actual); + @ParameterizedTest + @ValueSource(ints = { -1, 0, 5, 108 }) + void of_ValidYearAndInvalidQuarterValue_DateTimeException(int quarter) { + int year = 2024; + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, quarter); + }); + } - assertEquals("2023 Q1", actual.toString()); + @ParameterizedTest + @ValueSource(ints = { Year.MIN_VALUE - 1, Year.MAX_VALUE + 1 }) + void of_InvalidYearAndValidQuarterValue_DateTimeException(int year) { + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, 1); + }); + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, 2); + }); + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, 3); + }); + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, 4); + }); } @Test - @SuppressWarnings("null") - void of_InvalidQuarter_ThrowsNullPointerException() { - int year = 2023; - Quarter quarter = null; - - assertThrows(NullPointerException.class, () -> YearQuarter.of(year, quarter)); - } - - @Test - void of_ValidYearQuarter_GetsCorrectStartAndEndDate() { - - for (int year = 1990; year <= 2024; year++) { - for (int qrtr = 1; qrtr <= 4; qrtr++) { - Quarter quarter = Quarter.of(qrtr); - YearQuarter yearQuarter = YearQuarter.of(year, quarter); - - LocalDate expectedStartDate = quarter.firstMonthDay().atYear(year); - log.info("{} - expectedStartDate: {}", yearQuarter, expectedStartDate); - LocalDate expectedEndDate = quarter.lastMonthDay().atYear(year); - log.info("{} - expectedEndDate: {}", yearQuarter, expectedEndDate); - - assertEquals(expectedStartDate, yearQuarter.firstDate()); - assertEquals(expectedEndDate, yearQuarter.lastDate()); + void of_InvalidYearAndInvalidQuarterValue_DateTimeException() { + final int[] years = { Year.MIN_VALUE - 1, Year.MAX_VALUE + 1 }; + final int[] quarters = { -1, 0, 5, 108 }; + for (int year : years) { + final int yearValue = year; + for (int quarter : quarters) { + final int quarterValue = quarter; + assertThrows(DateTimeException.class, + () -> YearQuarter.of(yearValue, quarterValue)); } } } + // ================================ + // #endregion - of(int year, int quarter) + // ================================ + + // ================================ + // #region - of(int year, Quarter quarter) + // ================================ + + @ParameterizedTest + @ValueSource(ints = { 1, 2, 3, 4 }) + void of_ValidYearAndQuarter_CreatesYearQuarter(int quarterValue) { + { + int year = 2024; + Quarter quarter = Quarter.of(quarterValue); + YearQuarter yearQuarter = YearQuarter.of(year, quarter); + log.info("{}", yearQuarter); + assertEquals(year, yearQuarter.getYear()); + assertEquals(quarter, yearQuarter.getQuarter()); + assertEquals(quarterValue, yearQuarter.getQuarterValue()); + } + { + int year = Year.MIN_VALUE; + Quarter quarter = Quarter.of(quarterValue); + YearQuarter yearQuarter = YearQuarter.of(year, quarter); + assertEquals(year, yearQuarter.getYear()); + assertEquals(quarter, yearQuarter.getQuarter()); + assertEquals(quarterValue, yearQuarter.getQuarterValue()); + } + { + int year = Year.MAX_VALUE; + Quarter quarter = Quarter.of(quarterValue); + YearQuarter yearQuarter = YearQuarter.of(year, quarter); + assertEquals(year, yearQuarter.getYear()); + assertEquals(quarter, yearQuarter.getQuarter()); + assertEquals(quarterValue, yearQuarter.getQuarterValue()); + } + } + + @Test + void of_ValidYearAndNullQuarter_NullPointerException() { + int year = 2024; + assertThrows(NullPointerException.class, () -> { + YearQuarter.of(year, null); + }); + } + + @ParameterizedTest + @ValueSource(ints = { Year.MIN_VALUE - 1, Year.MAX_VALUE + 1 }) + void of_InvalidYearAndValidQuarter_DateTimeException(int year) { + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, Quarter.Q1); + }); + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, Quarter.Q2); + }); + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, Quarter.Q3); + }); + assertThrows(DateTimeException.class, () -> { + YearQuarter.of(year, Quarter.Q4); + }); + } + + @Test + void of_InvalidYearAndNullQuarter_DateTimeException() { + final int[] years = { Year.MIN_VALUE - 1, Year.MAX_VALUE + 1 }; + for (int year : years) { + final int yearValue = year; + assertThrows(DateTimeException.class, + () -> YearQuarter.of(yearValue, null)); + + } + } + + // ================================ + // #endregion - of(int year, Quarter quarter) + // ================================ + + // ================================ + // #region - of(LocalDate date) + // ================================ + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidLocalDate_CreatesYearQuarter_Q1(int year) { + { + LocalDate date = YearMonth.of(year, 1).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 1).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 2).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 2).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 3).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 3).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + } + @ParameterizedTest @ValueSource(ints = { - 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, - 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, - 2020, 2021, 2022, 2023, 2024 + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, }) - void testFirstYearMonth(int year) { + void of_ValidLocalDate_CreatesYearQuarter_Q2(int year) { + { + LocalDate date = YearMonth.of(year, 4).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 4).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 5).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 5).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 6).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 6).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + } + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidLocalDate_CreatesYearQuarter_Q3(int year) { + { + LocalDate date = YearMonth.of(year, 7).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 7).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 8).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 8).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 9).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 9).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + } + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidLocalDate_CreatesYearQuarter_Q4(int year) { + { + LocalDate date = YearMonth.of(year, 10).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 10).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 11).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 11).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 12).atDay(1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + LocalDate date = YearMonth.of(year, 12).atEndOfMonth(); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + } + + @Test + void of_NullLocalDate_NullPointerException() { + LocalDate date = null; + assertThrows(NullPointerException.class, () -> { + YearQuarter.of(date); + }); + } + + // ================================ + // #endregion - of(LocalDate date) + // ================================ + + // ================================ + // #region - of(Date date) + // ================================ + + @SuppressWarnings("deprecation") + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + 1, + 999999, + }) + void of_ValidDate_CreatesYearQuarter(int year) { + { + Date date = new Date(year - 1900, 1 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 3 - 1, 31, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 4 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 6 - 1, 30, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 7 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 9 - 1, 30, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 10 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + Date date = new Date(year - 1900, 12 - 1, 31, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + } + + @Test + void of_NullDate_NullPointerException() { + Date date = null; + assertThrows(NullPointerException.class, () -> { + YearQuarter.of(date); + }); + } + + // ================================ + // #endregion - of(Date date) + // ================================ + + // ================================ + // #region - of(Calendar date) + // ================================ + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + 1, + 999999, + }) + void of_ValidCalendar_CreatesYearQuarter(int year) { + Calendar date = Calendar.getInstance(); + { + date.set(year, 1 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + date.set(year, 3 - 1, 31, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(1, yq.getQuarterValue()); + assertSame(Quarter.Q1, yq.getQuarter()); + } + { + date.set(year, 4 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + date.set(year, 6 - 1, 30, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(2, yq.getQuarterValue()); + assertSame(Quarter.Q2, yq.getQuarter()); + } + { + date.set(year, 7 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + date.set(year, 9 - 1, 30, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(3, yq.getQuarterValue()); + assertSame(Quarter.Q3, yq.getQuarter()); + } + { + date.set(year, 10 - 1, 1); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + { + date.set(year, 12 - 1, 31, 23, 59, 59); + YearQuarter yq = YearQuarter.of(date); + assertEquals(year, yq.getYear()); + assertEquals(4, yq.getQuarterValue()); + assertSame(Quarter.Q4, yq.getQuarter()); + } + } + + @Test + void of_NullCalendar_NullPointerException() { + Calendar date = null; + assertThrows(NullPointerException.class, () -> { + YearQuarter.of(date); + }); + } + + // ================================ + // #endregion - of(Calendar date) + // ================================ + + // ================================ + // #region - of(YearMonth yearMonth) + // ================================ + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidYearMonth_CreatesYearMnoth_Q1(int year) { + { + YearMonth yearMonth = YearMonth.of(year, 1); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(1, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q1, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 2); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(1, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q1, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 3); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(1, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q1, yearQuarter.getQuarter()); + } + } + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidYearMonth_CreatesYearMnoth_Q2(int year) { + { + YearMonth yearMonth = YearMonth.of(year, 4); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(2, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q2, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 5); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(2, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q2, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 6); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(2, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q2, yearQuarter.getQuarter()); + } + } + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidYearMonth_CreatesYearMnoth_Q3(int year) { + { + YearMonth yearMonth = YearMonth.of(year, 7); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(3, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q3, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 8); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(3, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q3, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 9); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(3, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q3, yearQuarter.getQuarter()); + } + } + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_ValidYearMonth_CreatesYearMnoth_Q4(int year) { + { + YearMonth yearMonth = YearMonth.of(year, 10); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(4, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q4, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 11); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(4, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q4, yearQuarter.getQuarter()); + } + { + YearMonth yearMonth = YearMonth.of(year, 12); + YearQuarter yearQuarter = YearQuarter.of(yearMonth); + assertEquals(year, yearQuarter.getYear()); + assertEquals(4, yearQuarter.getQuarterValue()); + assertSame(Quarter.Q4, yearQuarter.getQuarter()); + } + } + + @ParameterizedTest + @ValueSource(ints = { + 2023, // 非闰年 + 2024, // 闰年 + Year.MIN_VALUE, + Year.MAX_VALUE, + }) + void of_NullYearMonth_CreatesYearMnoth_Q4(int year) { + YearMonth yearMonth = null; + assertThrows(NullPointerException.class, + () -> YearQuarter.of(yearMonth)); + } + + // ================================ + // #endregion - of(YearMonth yearMonth) + // ================================ + + // ================================ + // #region - firstDate & lastDate + // ================================ + + @ParameterizedTest + @ValueSource(ints = { 1949, 1990, 2000, 2008, 2023, 2024, Year.MIN_VALUE, Year.MAX_VALUE }) + void test_getFirstDate_And_getLastDate(int year) { + { + final int quarterValue = 1; + YearQuarter yearQuarter = YearQuarter.of(year, quarterValue); + + LocalDate expectedFirstDate = LocalDate.of(year, 1, 1); + log.info("{} - expectedFirstDate: {}", yearQuarter, expectedFirstDate); + LocalDate expectedLastDate = LocalDate.of(year, 3, 31); + log.info("{} - expectedLastDate: {}", yearQuarter, expectedLastDate); + + assertEquals(expectedFirstDate, yearQuarter.firstDate()); + assertEquals(expectedLastDate, yearQuarter.lastDate()); + } + { + final int quarterValue = 2; + YearQuarter yearQuarter = YearQuarter.of(year, quarterValue); + + LocalDate expectedFirstDate = LocalDate.of(year, 4, 1); + log.info("{} - expectedFirstDate: {}", yearQuarter, expectedFirstDate); + LocalDate expectedLastDate = LocalDate.of(year, 6, 30); + log.info("{} - expectedLastDate: {}", yearQuarter, expectedLastDate); + + assertEquals(expectedFirstDate, yearQuarter.firstDate()); + assertEquals(expectedLastDate, yearQuarter.lastDate()); + } + { + final int quarterValue = 3; + YearQuarter yearQuarter = YearQuarter.of(year, quarterValue); + + LocalDate expectedFirstDate = LocalDate.of(year, 7, 1); + log.info("{} - expectedFirstDate: {}", yearQuarter, expectedFirstDate); + LocalDate expectedLastDate = LocalDate.of(year, 9, 30); + log.info("{} - expectedLastDate: {}", yearQuarter, expectedLastDate); + + assertEquals(expectedFirstDate, yearQuarter.firstDate()); + assertEquals(expectedLastDate, yearQuarter.lastDate()); + } + { + final int quarterValue = 4; + YearQuarter yearQuarter = YearQuarter.of(year, quarterValue); + + LocalDate expectedFirstDate = LocalDate.of(year, 10, 1); + log.info("{} - expectedFirstDate: {}", yearQuarter, expectedFirstDate); + LocalDate expectedLastDate = LocalDate.of(year, 12, 31); + log.info("{} - expectedLastDate: {}", yearQuarter, expectedLastDate); + + assertEquals(expectedFirstDate, yearQuarter.firstDate()); + assertEquals(expectedLastDate, yearQuarter.lastDate()); + } + } + + // ================================ + // #endregion - firstDate & lastDate + // ================================ + + // ================================ + // #region - firstYearMonth & lastYearMonth + // ================================ + + @ParameterizedTest + @ValueSource(ints = { 1949, 1990, 2000, 2008, 2023, 2024, Year.MIN_VALUE, Year.MAX_VALUE }) + void test_firstYearMonth_And_lastYearMonth(int year) { YearQuarter yq; yq = YearQuarter.of(year, Quarter.Q1); @@ -102,6 +811,50 @@ public class YearQuarterTests { assertEquals(YearMonth.of(year, 12), yq.lastYearMonth()); } + // ================================ + // #endregion - firstYearMonth & lastYearMonth + // ================================ + + // ================================ + // #region - firstMonth & lastMonth + // ================================ + + @ParameterizedTest + @ValueSource(ints = { 1949, 1990, 2000, 2008, 2023, 2024, Year.MIN_VALUE, Year.MAX_VALUE }) + void testFirstMonthAndLastMonth(int year) { + YearQuarter q1 = YearQuarter.of(year, 1); + assertEquals(1, q1.firstMonthValue()); + assertEquals(Month.JANUARY, q1.firstMonth()); + assertEquals(3, q1.lastMonthValue()); + assertEquals(Month.MARCH, q1.lastMonth()); + + YearQuarter q2 = YearQuarter.of(year, 2); + assertEquals(4, q2.firstMonthValue()); + assertEquals(Month.APRIL, q2.firstMonth()); + assertEquals(6, q2.lastMonthValue()); + assertEquals(Month.JUNE, q2.lastMonth()); + + YearQuarter q3 = YearQuarter.of(year, 3); + assertEquals(7, q3.firstMonthValue()); + assertEquals(Month.JULY, q3.firstMonth()); + assertEquals(9, q3.lastMonthValue()); + assertEquals(Month.SEPTEMBER, q3.lastMonth()); + + YearQuarter q4 = YearQuarter.of(year, 4); + assertEquals(10, q4.firstMonthValue()); + assertEquals(Month.OCTOBER, q4.firstMonth()); + assertEquals(12, q4.lastMonthValue()); + assertEquals(Month.DECEMBER, q4.lastMonth()); + } + + // ================================ + // #endregion - firstMonth & lastMonth + // ================================ + + // ================================ + // #region - compareTo + // ================================ + @Test void testCompareTo() { int year1; @@ -124,23 +877,20 @@ public class YearQuarterTests { // 同年同季度 assertEquals(yearQuarter1, yearQuarter2); assertEquals(0, yearQuarter1.compareTo(yearQuarter2)); - } - else if (quarter1 < quarter2) { + } else if (quarter1 < quarter2) { assertNotEquals(yearQuarter1, yearQuarter2); assertTrue(yearQuarter1.isBefore(yearQuarter2)); assertFalse(yearQuarter1.isAfter(yearQuarter2)); assertFalse(yearQuarter2.isBefore(yearQuarter1)); assertTrue(yearQuarter2.isAfter(yearQuarter1)); - } - else if (quarter1 > quarter2) { + } else if (quarter1 > quarter2) { assertNotEquals(yearQuarter1, yearQuarter2); assertFalse(yearQuarter1.isBefore(yearQuarter2)); assertTrue(yearQuarter1.isAfter(yearQuarter2)); assertTrue(yearQuarter2.isBefore(yearQuarter1)); assertFalse(yearQuarter2.isAfter(yearQuarter1)); } - } - else { + } else { // 不同年 assertEquals(year1 - year2, yearQuarter1.compareTo(yearQuarter2)); assertNotEquals(0, yearQuarter1.compareTo(yearQuarter2)); @@ -150,8 +900,7 @@ public class YearQuarterTests { assertFalse(yearQuarter1.isAfter(yearQuarter2)); assertFalse(yearQuarter2.isBefore(yearQuarter1)); assertTrue(yearQuarter2.isAfter(yearQuarter1)); - } - else if (year1 > year2) { + } else if (year1 > year2) { assertNotEquals(yearQuarter1, yearQuarter2); assertFalse(yearQuarter1.isBefore(yearQuarter2)); assertTrue(yearQuarter1.isAfter(yearQuarter2)); @@ -162,4 +911,281 @@ public class YearQuarterTests { } } } + + // ================================ + // #endregion - compareTo + // ================================ + + @ParameterizedTest + @ValueSource(ints = { Year.MIN_VALUE + 25, Year.MAX_VALUE - 25, -1, 0, 1, 1949, 1990, 2000, 2008, 2023, 2024 }) + void testPlusQuartersAndMinusQuarters(int year) { + for (int quarter = 1; quarter <= 4; quarter++) { + YearQuarter yq1 = YearQuarter.of(year, quarter); + for (int quartersToAdd = -100; quartersToAdd <= 100; quartersToAdd++) { + YearQuarter plus = yq1.plusQuarters(quartersToAdd); + YearQuarter minus = yq1.minusQuarters(-quartersToAdd); + assertEquals(plus, minus); + + // offset: 表示自 公元 0000年以来,经历了多少季度。所以 0 表示 -0001,Q4; 1 表示 0000 Q1 + long offset = (year * 4L + quarter) + quartersToAdd; + if (offset > 0) { + assertEquals((offset - 1) / 4, plus.getYear()); + assertEquals(((offset - 1) % 4) + 1, plus.getQuarterValue()); + } else { + assertEquals((offset / 4 - 1), plus.getYear()); + assertEquals((4 + offset % 4), plus.getQuarterValue()); + } + } + } + } + + @ParameterizedTest + @ValueSource(ints = { Year.MIN_VALUE + 1, Year.MAX_VALUE - 1, -1, 0, 1, 1900, 1990, 2000, 2023, 2024 }) + void test_nextQuarter_And_lastQuarter(int year) { + int quarter; + + YearQuarter yq; + YearQuarter next; + YearQuarter last; + + quarter = 1; + yq = YearQuarter.of(year, quarter); + next = yq.nextQuarter(); + assertEquals(year, next.getYear()); + assertEquals(2, next.getQuarterValue()); + last = yq.lastQuarter(); + assertEquals(year - 1, last.getYear()); + assertEquals(4, last.getQuarterValue()); + + quarter = 2; + yq = YearQuarter.of(year, quarter); + next = yq.nextQuarter(); + assertEquals(year, next.getYear()); + assertEquals(3, next.getQuarterValue()); + last = yq.lastQuarter(); + assertEquals(year, last.getYear()); + assertEquals(1, last.getQuarterValue()); + + quarter = 3; + yq = YearQuarter.of(year, quarter); + next = yq.nextQuarter(); + assertEquals(year, next.getYear()); + assertEquals(4, next.getQuarterValue()); + last = yq.lastQuarter(); + assertEquals(year, last.getYear()); + assertEquals(2, last.getQuarterValue()); + + quarter = 4; + yq = YearQuarter.of(year, quarter); + next = yq.nextQuarter(); + assertEquals(year + 1, next.getYear()); + assertEquals(1, next.getQuarterValue()); + last = yq.lastQuarter(); + assertEquals(year, last.getYear()); + assertEquals(3, last.getQuarterValue()); + } + + @ParameterizedTest + @ValueSource(ints = { Year.MIN_VALUE + 100, Year.MAX_VALUE - 100, -1, 0, 1, 1949, 1990, 2000, 2008, 2023, 2024 }) + void test_PlusYearsAndMinusYears(int year) { + for (int yearToAdd = -100; yearToAdd <= 100; yearToAdd++) { + YearQuarter q1 = YearQuarter.of(year, Quarter.Q1); + YearQuarter plus = q1.plusYears(yearToAdd); + assertEquals(year + yearToAdd, plus.getYear()); + assertEquals(Quarter.Q1, plus.getQuarter()); + YearQuarter minus = q1.minusYears(yearToAdd); + assertEquals(Quarter.Q1, minus.getQuarter()); + assertEquals(year - yearToAdd, minus.getYear()); + + assertEquals(q1.plusYears(yearToAdd), q1.minusYears(-yearToAdd)); + } + } + + @ParameterizedTest + @ValueSource(ints = { Year.MIN_VALUE + 1, Year.MAX_VALUE - 1, -1, 0, 1, 1900, 1990, 2000, 2023, 2024 }) + void test_nextYear_And_lastYear(int year) { + int quarter; + + YearQuarter yq; + YearQuarter next; + YearQuarter last; + + quarter = 1; + yq = YearQuarter.of(year, quarter); + next = yq.nextYear(); + assertSame(Quarter.Q1, yq.getQuarter()); + assertEquals(year + 1, next.getYear()); + assertSame(Quarter.Q1, next.getQuarter()); + last = yq.lastYear(); + assertEquals(year - 1, last.getYear()); + assertSame(Quarter.Q1, last.getQuarter()); + + quarter = 2; + yq = YearQuarter.of(year, quarter); + next = yq.nextYear(); + assertSame(Quarter.Q2, yq.getQuarter()); + assertEquals(year + 1, next.getYear()); + assertSame(Quarter.Q2, next.getQuarter()); + last = yq.lastYear(); + assertEquals(year - 1, last.getYear()); + assertSame(Quarter.Q2, last.getQuarter()); + + quarter = 3; + yq = YearQuarter.of(year, quarter); + next = yq.nextYear(); + assertSame(Quarter.Q3, yq.getQuarter()); + assertEquals(year + 1, next.getYear()); + assertSame(Quarter.Q3, next.getQuarter()); + last = yq.lastYear(); + assertEquals(year - 1, last.getYear()); + assertSame(Quarter.Q3, last.getQuarter()); + + quarter = 4; + yq = YearQuarter.of(year, quarter); + next = yq.nextYear(); + assertSame(Quarter.Q4, yq.getQuarter()); + assertEquals(year + 1, next.getYear()); + assertSame(Quarter.Q4, next.getQuarter()); + last = yq.lastYear(); + assertEquals(year - 1, last.getYear()); + assertSame(Quarter.Q4, last.getQuarter()); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 0, 1, 1900, 2000, 2023, 2024, Year.MAX_VALUE, Year.MIN_VALUE }) + void test_compareTo_sameYear(int year) { + YearQuarter yq1 = YearQuarter.of(year, 1); + YearQuarter yq2 = YearQuarter.of(year, 2); + YearQuarter yq3 = YearQuarter.of(year, 3); + YearQuarter yq4 = YearQuarter.of(year, 4); + + assertTrue(yq1.equals(YearQuarter.of(year, Quarter.Q1))); // NOSONAR + assertTrue(yq1.compareTo(yq1) == 0); // NOSONAR + assertTrue(yq1.compareTo(yq2) < 0); + assertTrue(yq1.compareTo(yq3) < 0); + assertTrue(yq1.compareTo(yq4) < 0); + + assertTrue(yq2.equals(YearQuarter.of(year, Quarter.Q2))); // NOSONAR + assertTrue(yq2.compareTo(yq1) > 0); + assertTrue(yq2.compareTo(yq2) == 0); // NOSONAR + assertTrue(yq2.compareTo(yq3) < 0); + assertTrue(yq2.compareTo(yq4) < 0); + + assertTrue(yq3.equals(YearQuarter.of(year, Quarter.Q3))); // NOSONAR + assertTrue(yq3.compareTo(yq1) > 0); + assertTrue(yq3.compareTo(yq2) > 0); + assertTrue(yq3.compareTo(yq3) == 0); // NOSONAR + assertTrue(yq3.compareTo(yq4) < 0); + + assertTrue(yq4.equals(YearQuarter.of(year, Quarter.Q4))); // NOSONAR + assertTrue(yq4.compareTo(yq1) > 0); + assertTrue(yq4.compareTo(yq2) > 0); + assertTrue(yq4.compareTo(yq3) > 0); + assertTrue(yq4.compareTo(yq4) == 0); // NOSONAR + } + + @ParameterizedTest + @ValueSource(ints = { -1, 0, 1, 1900, 2000, 2023, 2024, Year.MAX_VALUE, Year.MIN_VALUE }) + void test_isBefore_sameYear(int year) { + YearQuarter yq1 = YearQuarter.of(year, 1); + YearQuarter yq2 = YearQuarter.of(year, 2); + YearQuarter yq3 = YearQuarter.of(year, 3); + YearQuarter yq4 = YearQuarter.of(year, 4); + + assertFalse(yq1.isBefore(YearQuarter.of(year, Quarter.Q1))); + assertTrue(yq1.isBefore(yq2)); + assertTrue(yq1.isBefore(yq3)); + assertTrue(yq1.isBefore(yq4)); + + assertFalse(yq2.isBefore(yq1)); + assertFalse(yq2.isBefore(YearQuarter.of(year, Quarter.Q2))); + assertTrue(yq2.isBefore(yq3)); + assertTrue(yq2.isBefore(yq4)); + + assertFalse(yq3.isBefore(yq1)); + assertFalse(yq3.isBefore(yq2)); + assertFalse(yq3.isBefore(YearQuarter.of(year, Quarter.Q3))); + assertTrue(yq3.isBefore(yq4)); + + assertFalse(yq4.isBefore(yq1)); + assertFalse(yq4.isBefore(yq2)); + assertFalse(yq4.isBefore(yq3)); + assertFalse(yq4.isBefore(YearQuarter.of(year, Quarter.Q4))); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 0, 1, 1900, 2000, 2023, 2024, Year.MAX_VALUE, Year.MIN_VALUE }) + void test_isAfter_sameYear(int year) { + YearQuarter yq1 = YearQuarter.of(year, 1); + YearQuarter yq2 = YearQuarter.of(year, 2); + YearQuarter yq3 = YearQuarter.of(year, 3); + YearQuarter yq4 = YearQuarter.of(year, 4); + + assertFalse(yq1.isAfter(YearQuarter.of(year, Quarter.Q1))); + assertFalse(yq1.isAfter(yq2)); + assertFalse(yq1.isAfter(yq3)); + assertFalse(yq1.isAfter(yq4)); + + assertTrue(yq2.isAfter(yq1)); + assertFalse(yq2.isAfter(YearQuarter.of(year, Quarter.Q2))); + assertFalse(yq2.isAfter(yq3)); + assertFalse(yq2.isAfter(yq4)); + + assertTrue(yq3.isAfter(yq1)); + assertTrue(yq3.isAfter(yq2)); + assertFalse(yq3.isAfter(YearQuarter.of(year, Quarter.Q3))); + assertFalse(yq3.isAfter(yq4)); + + assertTrue(yq4.isAfter(yq1)); + assertTrue(yq4.isAfter(yq2)); + assertTrue(yq4.isAfter(yq3)); + assertFalse(yq4.isAfter(YearQuarter.of(year, Quarter.Q4))); + } + + @Test + void test_compareTo_null() { + YearQuarter yq = YearQuarter.of(2024, 4); + assertThrows(NullPointerException.class, + () -> yq.compareTo(null)); + assertThrows(NullPointerException.class, + () -> yq.isBefore(null)); + assertThrows(NullPointerException.class, + () -> yq.isAfter(null)); + assertNotEquals(null, yq); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 0, 1, 1900, 2000, 2023, 2024, Year.MAX_VALUE - 1, Year.MIN_VALUE + 1 }) + void test_compareTo_differentYear(int year) { + for (int quarter1 = 1; quarter1 <= 4; quarter1++) { + YearQuarter yq = YearQuarter.of(year, quarter1); + for (int quarter2 = 1; quarter2 <= 4; quarter2++) { + // gt + assertTrue(yq.compareTo(YearQuarter.of(year + 1, quarter2)) < 0); + assertTrue(yq.isBefore(YearQuarter.of(year + 1, quarter2))); + assertTrue(YearQuarter.of(year + 1, quarter2).compareTo(yq) > 0); + assertTrue(YearQuarter.of(year + 1, quarter2).isAfter(yq)); + // lt + assertTrue(yq.compareTo(YearQuarter.of(year - 1, quarter2)) > 0); + assertTrue(yq.isAfter(YearQuarter.of(year - 1, quarter2))); + assertTrue(YearQuarter.of(year - 1, quarter2).compareTo(yq) < 0); + assertTrue(YearQuarter.of(year - 1, quarter2).isBefore(yq)); + } + } + } + + @ParameterizedTest + @ValueSource(ints = { -1, 0, 1, 1900, 2000, 2023, 2024, Year.MAX_VALUE, Year.MIN_VALUE }) + void test_min_And_max_sameYear(int year) { + YearQuarter yq1 = YearQuarter.of(year, 1); + YearQuarter anotherYq1 = YearQuarter.of(year, 1); + + assertEquals(yq1, YearQuarter.max(yq1, anotherYq1)); + assertEquals(yq1, YearQuarter.min(yq1, anotherYq1)); + + YearQuarter yq2 = YearQuarter.of(year, 2); + assertEquals(yq2, YearQuarter.max(yq1, yq2)); + assertEquals(yq1, YearQuarter.min(yq1, yq2)); + + } } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/ArrayToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/ArrayToolsTests.java index 6b23fb2..32423a1 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/ArrayToolsTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/ArrayToolsTests.java @@ -16,21 +16,1075 @@ package xyz.zhouxy.plusone.commons.util; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; + import org.junit.jupiter.api.Test; -@SuppressWarnings("all") +@SuppressWarnings("null") public class ArrayToolsTests { + + static final String[] NULL_STRING_ARRAY = null; + static final Integer[] NULL_INTEGER_ARRAY = null; + static final char[] NULL_CHAR_ARRAY = null; + static final byte[] NULL_BYTE_ARRAY = null; + static final short[] NULL_SHORT_ARRAY = null; + static final int[] NULL_INT_ARRAY = null; + static final long[] NULL_LONG_ARRAY = null; + static final float[] NULL_FLOAT_ARRAY = null; + static final double[] NULL_DOUBLE_ARRAY = null; + + static final String[] EMPTY_STRING_ARRAY = {}; + static final Integer[] EMPTY_INTEGER_ARRAY = {}; + static final char[] EMPTY_CHAR_ARRAY = {}; + static final byte[] EMPTY_BYTE_ARRAY = {}; + static final short[] EMPTY_SHORT_ARRAY = {}; + static final int[] EMPTY_INT_ARRAY = {}; + static final long[] EMPTY_LONG_ARRAY = {}; + static final float[] EMPTY_FLOAT_ARRAY = {}; + static final double[] EMPTY_DOUBLE_ARRAY = {}; + + // ================================ + // #region - isNullOrEmpty + // ================================ + @Test - void testIsAllNotNull() { - assertTrue(ArrayTools.isAllElementsNotNull(new Object[] { 1L, 2, 3.0, "Java" })); - assertFalse(ArrayTools.isAllElementsNotNull(new Object[] { 1L, 2, 3.0, "Java", null })); - assertFalse(ArrayTools.isAllElementsNotNull(new Object[] { null, 1L, 2, 3.0, "Java" })); - assertTrue(ArrayTools.isAllElementsNotNull(new Object[] {})); - assertThrows(IllegalArgumentException.class, - () -> ArrayTools.isAllElementsNotNull(null)); + void isNullOrEmpty_NullArray_ReturnsTrue() { + assertAll( + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_STRING_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_INTEGER_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_CHAR_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_BYTE_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_SHORT_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_INT_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_LONG_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_FLOAT_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(NULL_DOUBLE_ARRAY))); } + + @Test + void isNullOrEmpty_EmptyArray_ReturnsTrue() { + assertAll( + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_STRING_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_INTEGER_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_CHAR_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_BYTE_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_SHORT_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_INT_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_LONG_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_FLOAT_ARRAY)), + () -> assertTrue(ArrayTools.isNullOrEmpty(EMPTY_DOUBLE_ARRAY))); + } + + @Test + void isNullOrEmpty_NonEmptyArray_ReturnsFalse() { + assertAll( + () -> assertFalse(ArrayTools.isNullOrEmpty(new String[] { "a" })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new Integer[] { 1 })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new char[] { 'a' })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new byte[] { 1 })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new short[] { 1 })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new int[] { 1 })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new long[] { 1 })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new float[] { 1 })), + () -> assertFalse(ArrayTools.isNullOrEmpty(new double[] { 1 }))); + } + + // ================================ + // #endregion - isNullOrEmpty + // ================================ + + // ================================ + // #region - isNotEmpty + // ================================ + + @Test + void isNotEmpty_NullArray_ReturnsFalse() { + assertAll( + () -> assertFalse(ArrayTools.isNotEmpty(NULL_STRING_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_INTEGER_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_CHAR_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_BYTE_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_SHORT_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_INT_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_LONG_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_FLOAT_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(NULL_DOUBLE_ARRAY))); + } + + @Test + void isNotEmpty_EmptyArray_ReturnsFalse() { + assertAll( + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_STRING_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_INTEGER_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_CHAR_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_BYTE_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_SHORT_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_INT_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_LONG_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_FLOAT_ARRAY)), + () -> assertFalse(ArrayTools.isNotEmpty(EMPTY_DOUBLE_ARRAY))); + } + + @Test + void isNotEmpty_NonEmptyArray_ReturnsTrue() { + assertAll( + () -> assertTrue(ArrayTools.isNotEmpty(new String[] { "a" })), + () -> assertTrue(ArrayTools.isNotEmpty(new Integer[] { 1 })), + () -> assertTrue(ArrayTools.isNotEmpty(new char[] { 'a' })), + () -> assertTrue(ArrayTools.isNotEmpty(new byte[] { 1 })), + () -> assertTrue(ArrayTools.isNotEmpty(new short[] { 1 })), + () -> assertTrue(ArrayTools.isNotEmpty(new int[] { 1 })), + () -> assertTrue(ArrayTools.isNotEmpty(new long[] { 1 })), + () -> assertTrue(ArrayTools.isNotEmpty(new float[] { 1 })), + () -> assertTrue(ArrayTools.isNotEmpty(new double[] { 1 }))); + } + + // ================================ + // #endregion - isNotEmpty + // ================================ + + // ================================ + // #region - isAllElementsNotNull + // ================================ + + @Test + void isAllElementsNotNull_NullArray_ThrowsException() { + assertThrows(IllegalArgumentException.class, () -> ArrayTools.isAllElementsNotNull(NULL_STRING_ARRAY)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.isAllElementsNotNull(NULL_INTEGER_ARRAY)); + } + + @Test + void isAllElementsNotNull_EmptyArray_ReturnsTrue() { + assertTrue(ArrayTools.isAllElementsNotNull(EMPTY_STRING_ARRAY)); + assertTrue(ArrayTools.isAllElementsNotNull(EMPTY_INTEGER_ARRAY)); + } + + @Test + void isAllElementsNotNull_ArrayWithNullElement_ReturnsFalse() { + assertFalse(ArrayTools.isAllElementsNotNull(new String[] { "a", null })); + assertFalse(ArrayTools.isAllElementsNotNull(new Integer[] { 1, null })); + } + + @Test + void isAllElementsNotNull_ArrayWithoutNullElements_ReturnsTrue() { + assertTrue(ArrayTools.isAllElementsNotNull(new String[] { "a", "b" })); + assertTrue(ArrayTools.isAllElementsNotNull(new Integer[] { 1, 2 })); + } + + // ================================ + // #endregion - isAllElementsNotNull + // ================================ + + // ================================ + // #region - concat + // ================================ + + static final List charArrays; + static final List byteArrays; + static final List shortArrays; + static final List intArrays; + static final List longArrays; + static final List floatArrays; + static final List doubleArrays; + + static { + charArrays = new ArrayList<>(); + charArrays.add(null); + charArrays.add(new char[0]); + charArrays.add(new char[] { 'a', 'b' }); + charArrays.add(new char[] { 'c', 'd', 'e' }); + + byteArrays = new ArrayList<>(); + byteArrays.add(null); + byteArrays.add(new byte[0]); + byteArrays.add(new byte[] { 1, 2 }); + byteArrays.add(new byte[] { 3, 4, 5 }); + + shortArrays = new ArrayList<>(); + shortArrays.add(null); + shortArrays.add(new short[0]); + shortArrays.add(new short[] { 10, 20 }); + shortArrays.add(new short[] { 30, 40, 50 }); + + intArrays = new ArrayList<>(); + intArrays.add(null); + intArrays.add(new int[0]); + intArrays.add(new int[] { 100, 200 }); + intArrays.add(new int[] { 300, 400, 500 }); + + longArrays = new ArrayList<>(); + longArrays.add(null); + longArrays.add(new long[0]); + longArrays.add(new long[] { 1000L, 2000L }); + longArrays.add(new long[] { 3000L, 4000L, 5000L }); + + floatArrays = new ArrayList<>(); + floatArrays.add(null); + floatArrays.add(new float[0]); + floatArrays.add(new float[] { 1000.1f, 2000.2f }); + floatArrays.add(new float[] { 3000.3f, 4000.4f, 5000.5f }); + + doubleArrays = new ArrayList<>(); + doubleArrays.add(null); + doubleArrays.add(new double[0]); + doubleArrays.add(new double[] { 1000.1d, 2000.2d }); + doubleArrays.add(new double[] { 3000.3d, 4000.4d, 5000.5d }); + } + + @Test + void concat_NullOrEmptyCollection_ReturnsEmptyArray() { + assertEquals(0, ArrayTools.concatCharArray(null).length); + assertEquals(0, ArrayTools.concatCharArray(Collections.emptyList()).length); + + assertEquals(0, ArrayTools.concatByteArray(null).length); + assertEquals(0, ArrayTools.concatByteArray(Collections.emptyList()).length); + + assertEquals(0, ArrayTools.concatShortArray(null).length); + assertEquals(0, ArrayTools.concatShortArray(Collections.emptyList()).length); + + assertEquals(0, ArrayTools.concatIntArray(null).length); + assertEquals(0, ArrayTools.concatIntArray(Collections.emptyList()).length); + + assertEquals(0, ArrayTools.concatLongArray(null).length); + assertEquals(0, ArrayTools.concatLongArray(Collections.emptyList()).length); + + assertEquals(0, ArrayTools.concatFloatArray(null).length); + assertEquals(0, ArrayTools.concatFloatArray(Collections.emptyList()).length); + + assertEquals(0, ArrayTools.concatDoubleArray(null).length); + assertEquals(0, ArrayTools.concatDoubleArray(Collections.emptyList()).length); + } + + @Test + void concat_ValidCollection_ReturnsConcatenatedArray() { + assertArrayEquals(new char[] { 'a', 'b', 'c', 'd', 'e' }, + ArrayTools.concatCharArray(charArrays)); + + assertArrayEquals(new byte[] { 1, 2, 3, 4, 5 }, + ArrayTools.concatByteArray(byteArrays)); + + assertArrayEquals(new short[] { 10, 20, 30, 40, 50 }, + ArrayTools.concatShortArray(shortArrays)); + + assertArrayEquals(new int[] { 100, 200, 300, 400, 500 }, + ArrayTools.concatIntArray(intArrays)); + + assertArrayEquals(new long[] { 1000L, 2000L, 3000L, 4000L, 5000L }, + ArrayTools.concatLongArray(longArrays)); + + assertArrayEquals(new float[] { 1000.1f, 2000.2f, 3000.3f, 4000.4f, 5000.5f }, + ArrayTools.concatFloatArray(floatArrays)); + + assertArrayEquals(new double[] { 1000.1d, 2000.2d, 3000.3d, 4000.4d, 5000.5d }, + ArrayTools.concatDoubleArray(doubleArrays)); + } + + @Test + void concatToList_NullOrEmptyCollection_ReturnsEmptyList() { + assertTrue(ArrayTools.concatToList(null).isEmpty()); + assertTrue(ArrayTools.concatToList(Collections.emptyList()).isEmpty()); + } + + @Test + void concatToList_ValidCollection_ReturnsConcatenatedList() { + Character[] charArray1 = { 'a', 'b' }; + Character[] charArray2 = { 'c', 'd', 'e' }; + List expected = Arrays.asList('a', 'b', 'c', 'd', 'e'); + List result = ArrayTools.concatToList(Arrays.asList(charArray1, charArray2)); + assertEquals(expected, result); + } + + // ================================ + // #endregion + // ================================ + + // ================================ + // #region - repeat + // ================================ + + @Test + void repeat_NullArray_ThrowsException() { + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_CHAR_ARRAY, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_BYTE_ARRAY, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_SHORT_ARRAY, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_INT_ARRAY, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_LONG_ARRAY, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_FLOAT_ARRAY, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_DOUBLE_ARRAY, 2)); + + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_CHAR_ARRAY, 2, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_BYTE_ARRAY, 2, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_SHORT_ARRAY, 2, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_INT_ARRAY, 2, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_LONG_ARRAY, 2, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_FLOAT_ARRAY, 2, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(NULL_DOUBLE_ARRAY, 2, 2)); + } + + @Test + void repeat_NegativeTimes_ThrowsException() { + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new char[]{ 'a' }, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new byte[]{ 1 }, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new short[]{ 1 }, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new int[]{ 1 }, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new long[]{ 1 }, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new float[]{ 1 }, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new double[]{ 1 }, -1)); + + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new char[]{ 'a' }, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new byte[]{ 1 }, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new short[]{ 1 }, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new int[]{ 1 }, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new long[]{ 1 }, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new float[]{ 1 }, -1, 2)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new double[]{ 1 }, -1, 2)); + } + + @Test + void repeat_NegativeMaxLength_ThrowsException() { + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new char[]{ 'a' }, 2, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new byte[]{ 1 }, 2, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new short[]{ 1 }, 2, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new int[]{ 1 }, 2, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new long[]{ 1 }, 2, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new float[]{ 1 }, 2, -1)); + assertThrows(IllegalArgumentException.class, () -> ArrayTools.repeat(new double[]{ 1 }, 2, -1)); + } + + @Test + void repeat_ZeroTimes_ReturnsEmptyArray() { + assertEquals(0, ArrayTools.repeat(new char[]{ 'a' }, 0).length); + assertEquals(0, ArrayTools.repeat(new byte[]{ 1 }, 0).length); + assertEquals(0, ArrayTools.repeat(new short[]{ 1 }, 0).length); + assertEquals(0, ArrayTools.repeat(new int[]{ 1 }, 0).length); + assertEquals(0, ArrayTools.repeat(new long[]{ 1 }, 0).length); + assertEquals(0, ArrayTools.repeat(new float[]{ 1 }, 0).length); + assertEquals(0, ArrayTools.repeat(new double[]{ 1 }, 0).length); + } + + @Test + void repeat_NormalCase_RepeatsArray() { + assertArrayEquals(new char[] { 'a', 'b', 'a', 'b', 'a', 'b' }, ArrayTools.repeat(new char[] { 'a', 'b' }, 3)); + assertArrayEquals(new byte[] { 1, 2, 1, 2, 1, 2 }, ArrayTools.repeat(new byte[] { 1, 2 }, 3)); + assertArrayEquals(new short[] { 1, 2, 1, 2, 1, 2 }, ArrayTools.repeat(new short[] { 1, 2 }, 3)); + assertArrayEquals(new int[] { 1, 2, 1, 2, 1, 2 }, ArrayTools.repeat(new int[] { 1, 2 }, 3)); + assertArrayEquals(new long[] { 1L, 2L, 1L, 2L, 1L, 2L }, ArrayTools.repeat(new long[] { 1L, 2L }, 3)); + assertArrayEquals(new float[] { 1.1F, 2.2F, 1.1F, 2.2F, 1.1F, 2.2F }, ArrayTools.repeat(new float[] { 1.1F, 2.2F }, 3)); + assertArrayEquals(new double[] { 1.12, 2.23, 1.12, 2.23, 1.12, 2.23 }, ArrayTools.repeat(new double[] { 1.12, 2.23 }, 3)); + } + + @Test + void repeat_WithMaxLength_TruncatesResult() { + assertArrayEquals(new char[] { 'a', 'b', 'a', 'b', 'a' }, ArrayTools.repeat(new char[] { 'a', 'b' }, 3, 5)); + assertArrayEquals(new byte[] { 1, 2, 1, 2, 1 }, ArrayTools.repeat(new byte[] { 1, 2 }, 3, 5)); + assertArrayEquals(new short[] { 1, 2, 1, 2, 1 }, ArrayTools.repeat(new short[] { 1, 2 }, 3, 5)); + assertArrayEquals(new int[] { 1, 2, 1, 2, 1 }, ArrayTools.repeat(new int[] { 1, 2 }, 3, 5)); + assertArrayEquals(new long[] { 1L, 2L, 1L, 2L, 1L }, ArrayTools.repeat(new long[] { 1L, 2L }, 3, 5)); + assertArrayEquals(new float[] { 1.1F, 2.2F, 1.1F, 2.2F, 1.1F }, ArrayTools.repeat(new float[] { 1.1F, 2.2F }, 3, 5)); + assertArrayEquals(new double[] { 1.12, 2.23, 1.12, 2.23, 1.12 }, ArrayTools.repeat(new double[] { 1.12, 2.23 }, 3, 5)); + } + + // ================================ + // #endregion + // ================================ + + // ================================ + // #region - fill + // ================================ + + @Test + void fill_WithValues() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, new char[] {'a', 'b', 'c'}); + assertArrayEquals(new char[] { 'a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c', 'a' }, charArray); + ArrayTools.fill(charArray, "abcd"); + assertArrayEquals(new char[] { 'a', 'b', 'c', 'd', 'a', 'b', 'c', 'd', 'a', 'b' }, charArray); + + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, new byte[] { 1, 2, 3 }); + assertArrayEquals(new byte[] { 1, 2, 3, 1, 2, 3, 1, 2, 3, 1 }, byteArray); + + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, new short[] { 1, 2, 3 }); + assertArrayEquals(new short[] { 1, 2, 3, 1, 2, 3, 1, 2, 3, 1 }, shortArray); + + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, new int[] { 1, 2, 3 }); + assertArrayEquals(new int[] { 1, 2, 3, 1, 2, 3, 1, 2, 3, 1 }, intArray); + + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, new long[] { 1, 2, 3 }); + assertArrayEquals(new long[] { 1, 2, 3, 1, 2, 3, 1, 2, 3, 1 }, longArray); + + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, new float[] { 1.1F, 2.2F, 3.3F }); + assertArrayEquals(new float[] { 1.1F, 2.2F, 3.3F, 1.1F, 2.2F, 3.3F, 1.1F, 2.2F, 3.3F, 1.1F }, floatArray); + + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, new double[] { 1.1, 2.2, 3.3 }); + assertArrayEquals(new double[] { 1.1, 2.2, 3.3, 1.1, 2.2, 3.3, 1.1, 2.2, 3.3, 1.1 }, doubleArray); + + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, new String[] { "aa", "bb", "cc" }); + assertArrayEquals(new String[] { "aa", "bb", "cc", "aa", "bb", "cc", "aa", "bb", "cc", "aa" }, stringArray); + } + + @Test + void fill_WithEmptyValues() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, EMPTY_CHAR_ARRAY); + assertArrayEquals(new char[10], charArray); + ArrayTools.fill(charArray, ""); + assertArrayEquals(new char[10], charArray); + + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, EMPTY_BYTE_ARRAY); + assertArrayEquals(new byte[10], byteArray); + + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, EMPTY_SHORT_ARRAY); + assertArrayEquals(new short[10], shortArray); + + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, EMPTY_INT_ARRAY); + assertArrayEquals(new int[10], intArray); + + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, EMPTY_LONG_ARRAY); + assertArrayEquals(new long[10], longArray); + + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, EMPTY_FLOAT_ARRAY); + assertArrayEquals(new float[10], floatArray); + + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, EMPTY_DOUBLE_ARRAY); + assertArrayEquals(new double[10], doubleArray); + + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, EMPTY_STRING_ARRAY); + assertArrayEquals(new String[10], stringArray); + } + + @Test + void fill_WithNullValues() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, NULL_CHAR_ARRAY); + assertArrayEquals(new char[10], charArray); + ArrayTools.fill(charArray, (String) null); + assertArrayEquals(new char[10], charArray); + + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, NULL_BYTE_ARRAY); + assertArrayEquals(new byte[10], byteArray); + + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, NULL_SHORT_ARRAY); + assertArrayEquals(new short[10], shortArray); + + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, NULL_INT_ARRAY); + assertArrayEquals(new int[10], intArray); + + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, NULL_LONG_ARRAY); + assertArrayEquals(new long[10], longArray); + + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, NULL_FLOAT_ARRAY); + assertArrayEquals(new float[10], floatArray); + + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, NULL_DOUBLE_ARRAY); + assertArrayEquals(new double[10], doubleArray); + + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, NULL_STRING_ARRAY); + assertArrayEquals(new String[10], stringArray); + } + + @Test + void fill_WithValuesInRange() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, 2, 8, new char[] { 'x', 'y' }); + assertArrayEquals(new char[] { '\0', '\0', 'x', 'y', 'x', 'y', 'x', 'y', '\0', '\0' }, charArray); + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, 2, 8, new byte[] { 1, 2 }); + assertArrayEquals(new byte[] { 0, 0, 1, 2, 1, 2, 1, 2, 0, 0 }, byteArray); + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, 2, 8, new short[] { 1, 2 }); + assertArrayEquals(new short[] { 0, 0, 1, 2, 1, 2, 1, 2, 0, 0 }, shortArray); + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, 2, 8, new int[] { 1, 2 }); + assertArrayEquals(new int[] { 0, 0, 1, 2, 1, 2, 1, 2, 0, 0 }, intArray); + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, 2, 8, new long[] { 1, 2 }); + assertArrayEquals(new long[] { 0, 0, 1, 2, 1, 2, 1, 2, 0, 0 }, longArray); + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, 2, 8, new float[] { 1.1F, 2.2F }); + assertArrayEquals(new float[] { 0F, 0F, 1.1F, 2.2F, 1.1F, 2.2F, 1.1F, 2.2F, 0F, 0F }, floatArray); + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, 2, 8, new double[] { 0.1, 0.2 }); + assertArrayEquals(new double[] { 0.0, 0.0, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.0, 0.0 }, doubleArray); + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, 2, 8, new String[] { "Java", "C++" }); + assertArrayEquals(new String[] { null, null, "Java", "C++", "Java", "C++", "Java", "C++", null, null }, + stringArray); + } + + @Test + void fill_WithValues_OutOfRange() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, 10, 20, new char[] { 'x', 'y' }); + assertArrayEquals(new char[10], charArray); + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, 10, 20, new byte[] { 1, 2 }); + assertArrayEquals(new byte[10], byteArray); + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, 10, 20, new short[] { 1, 2 }); + assertArrayEquals(new short[10], shortArray); + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, 10, 20, new int[] { 1, 2 }); + assertArrayEquals(new int[10], intArray); + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, 10, 20, new long[] { 1, 2 }); + assertArrayEquals(new long[10], longArray); + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, 10, 20, new float[] { 1.1F, 2.2F }); + assertArrayEquals(new float[10], floatArray); + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, 10, 20, new double[] { 0.1, 0.2 }); + assertArrayEquals(new double[10], doubleArray); + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, 10, 20, new String[] { "Java", "C++" }); + assertArrayEquals(new String[10], stringArray); + } + + @Test + void fill_WithValues_NegativeRange() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, -5, 5, new char[] {'w'}); + assertArrayEquals(new char[]{'w', 'w', 'w', 'w', 'w', '\0', '\0', '\0', '\0', '\0'}, charArray); + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, -5, 5, new byte[] {108}); + assertArrayEquals(new byte[]{108, 108, 108, 108, 108, 0, 0, 0, 0, 0}, byteArray); + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, -5, 5, new short[] {108}); + assertArrayEquals(new short[]{108, 108, 108, 108, 108, 0, 0, 0, 0, 0}, shortArray); + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, -5, 5, new int[] {108}); + assertArrayEquals(new int[]{108, 108, 108, 108, 108, 0, 0, 0, 0, 0}, intArray); + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, -5, 5, new long[] {108L}); + assertArrayEquals(new long[]{108L, 108L, 108L, 108L, 108L, 0L, 0L, 0L, 0L, 0L}, longArray); + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, -5, 5, new float[] {108.01F}); + assertArrayEquals(new float[]{108.01F, 108.01F, 108.01F, 108.01F, 108.01F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}, floatArray); + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, -5, 5, new double[] {108.01}); + assertArrayEquals(new double[]{108.01, 108.01, 108.01, 108.01, 108.01, 0.0, 0.0, 0.0, 0.0, 0.0}, doubleArray); + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, -5, 5, new String[] {"test"}); + assertArrayEquals(new String[]{"test", "test", "test", "test", "test", null, null, null, null, null}, stringArray); + } + + @Test + void fill_WithValues_ExactRange() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, 0, 10, new char[] {'w'}); + assertArrayEquals(new char[]{'w', 'w', 'w', 'w', 'w', 'w', 'w', 'w', 'w', 'w'}, charArray); + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, 0, 10, new byte[] {108}); + assertArrayEquals(new byte[]{108, 108, 108, 108, 108, 108, 108, 108, 108, 108}, byteArray); + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, 0, 10, new short[] {108}); + assertArrayEquals(new short[]{108, 108, 108, 108, 108, 108, 108, 108, 108, 108}, shortArray); + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, 0, 10, new int[] {108}); + assertArrayEquals(new int[]{108, 108, 108, 108, 108, 108, 108, 108, 108, 108}, intArray); + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, 0, 10, new long[] {108L}); + assertArrayEquals(new long[]{108L, 108L, 108L, 108L, 108L, 108L, 108L, 108L, 108L, 108L}, longArray); + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, 0, 10, new float[] {108.01F}); + assertArrayEquals(new float[]{108.01F, 108.01F, 108.01F, 108.01F, 108.01F, 108.01F, 108.01F, 108.01F, 108.01F, 108.01F}, floatArray); + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, 0, 10, new double[] {108.01}); + assertArrayEquals(new double[]{108.01, 108.01, 108.01, 108.01, 108.01, 108.01, 108.01, 108.01, 108.01, 108.01}, doubleArray); + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, 0, 10, new String[] {"test"}); + assertArrayEquals(new String[]{"test", "test", "test", "test", "test", "test", "test", "test", "test", "test"}, stringArray); + } + + @Test + void fill_WithValues_Overlap() { + // fill - char + char[] charArray = new char[10]; + ArrayTools.fill(charArray, 0, 5, new char[] {'a', 'b', 'c', 'd', 'e', 'f'}); + assertArrayEquals(new char[]{'a', 'b', 'c', 'd', 'e', '\0', '\0', '\0', '\0', '\0'}, charArray); + // fill - byte + byte[] byteArray = new byte[10]; + ArrayTools.fill(byteArray, 0, 5, new byte[] {1, 2, 3, 4, 5, 6}); + assertArrayEquals(new byte[]{1, 2, 3, 4, 5, 0, 0, 0, 0, 0}, byteArray); + // fill - short + short[] shortArray = new short[10]; + ArrayTools.fill(shortArray, 0, 5, new short[] {1, 2, 3, 4, 5, 6}); + assertArrayEquals(new short[]{1, 2, 3, 4, 5, 0, 0, 0, 0, 0}, shortArray); + // fill - int + int[] intArray = new int[10]; + ArrayTools.fill(intArray, 0, 5, new int[] {1, 2, 3, 4, 5, 6}); + assertArrayEquals(new int[]{1, 2, 3, 4, 5, 0, 0, 0, 0, 0}, intArray); + // fill - long + long[] longArray = new long[10]; + ArrayTools.fill(longArray, 0, 5, new long[] {1L, 2L, 3L, 4L, 5L, 6L}); + assertArrayEquals(new long[]{1L, 2L, 3L, 4L, 5L, 0L, 0L, 0L, 0L, 0L}, longArray); + // fill - float + float[] floatArray = new float[10]; + ArrayTools.fill(floatArray, 0, 5, new float[] {1.1F, 2.2F, 3.3F, 4.4F, 5.5F, 6.6F}); + assertArrayEquals(new float[]{1.1F, 2.2F, 3.3F, 4.4F, 5.5F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F}, floatArray); + // fill - double + double[] doubleArray = new double[10]; + ArrayTools.fill(doubleArray, 0, 5, new double[] {1.1, 2.2, 3.3, 4.4, 5.5, 6.6}); + assertArrayEquals(new double[]{1.1, 2.2, 3.3, 4.4, 5.5, 0.0, 0.0, 0.0, 0.0, 0.0}, doubleArray); + // fill - T + String[] stringArray = new String[10]; + ArrayTools.fill(stringArray, 0, 5, new String[] {"aaa", "bbb", "ccc", "ddd", "eee", "fff"}); + assertArrayEquals(new String[]{"aaa", "bbb", "ccc", "ddd", "eee", null, null, null, null, null}, stringArray); + } + + // ================================ + // #endregion + // ================================ + + // ================================ + // #region - indexOf + // ================================ + + @Test + void indexOfWithPredicate_NullPredicate_ThrowsNullPointerException() { + assertThrows(NullPointerException.class, () -> ArrayTools.indexOfWithPredicate(new String[] {}, null)); + } + + @Test + void indexOfWithPredicate_NullArray_ReturnsNotFoundIndex() { + Predicate predicate = s -> s.equals("test"); + int result = ArrayTools.indexOfWithPredicate(null, predicate); + assertEquals(ArrayTools.NOT_FOUND_INDEX, result); + } + + @Test + void indexOfWithPredicate_EmptyArray_ReturnsNotFoundIndex() { + Predicate predicate = s -> s.equals("test"); + int result = ArrayTools.indexOfWithPredicate(new String[] {}, predicate); + assertEquals(ArrayTools.NOT_FOUND_INDEX, result); + } + + @Test + void indexOfWithPredicate_ArrayContainsMatchingElement_ReturnsIndex() { + String[] array = { "apple", "banana", "cherry" }; + Predicate predicate = s -> s.equals("banana"); + int result = ArrayTools.indexOfWithPredicate(array, predicate); + assertEquals(1, result); + } + + @Test + void indexOfWithPredicate_ArrayDoesNotContainMatchingElement_ReturnsNotFoundIndex() { + String[] array = { "apple", "banana", "cherry" }; + Predicate predicate = s -> s.equals("orange"); + int result = ArrayTools.indexOfWithPredicate(array, predicate); + assertEquals(ArrayTools.NOT_FOUND_INDEX, result); + } + + @Test + void indexOf_NullArray_ReturnsNotFound() { + // T + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((String[]) null, "test")); + // char + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((char[]) null, 'a')); + // byte + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((byte[]) null, (byte) 1)); + // short + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((short[]) null, (short) 1)); + // int + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((int[]) null, 1)); + // long + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((long[]) null, 1L)); + // float + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((float[]) null, 1.23F)); + // double + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf((double[]) null, 1.23)); + } + + @Test + void indexOf_EmptyArray_ReturnsNotFound() { + // T + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new String[] {}, "test")); + // char + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new char[] {}, 'a')); + // byte + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new byte[] {}, (byte) 1)); + // short + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new short[] {}, (short) 1)); + // int + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new int[] {}, 1)); + // long + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new long[] {}, 1L)); + // float + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new float[] {}, 1.23F)); + // double + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new double[] {}, 1.23)); + } + + @Test + void indexOf_ObjectFound_ReturnsIndex() { + // T + assertEquals(1, ArrayTools.indexOf(new String[] { "a", "b", "c" }, "b")); + // char + assertEquals(1, ArrayTools.indexOf(new char[] { 'a', 'b', 'c' }, 'b')); + // byte + assertEquals(1, ArrayTools.indexOf(new byte[] { 1, 2, 3 }, (byte) 2)); + // short + assertEquals(1, ArrayTools.indexOf(new short[] { 1, 2, 3 }, (short) 2)); + // int + assertEquals(1, ArrayTools.indexOf(new int[] { 1, 2, 3 }, 2)); + // long + assertEquals(1, ArrayTools.indexOf(new long[] { 1000000L, 2000000L, 3000000L }, 2000000L)); + // float + assertEquals(1, ArrayTools.indexOf(new float[] { 1.11F, 2.22F, 3.33F }, 2.22F)); + // double + assertEquals(1, ArrayTools.indexOf(new double[] { 1.11, 2.22, 3.33 }, 2.22)); + } + + @Test + void indexOf_ObjectNotFound_ReturnsNotFound() { + // T + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new String[] { "a", "b", "c" }, "d")); + // char + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new char[] { 'a', 'b', 'c' }, 'd')); + // byte + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new byte[] { 1, 2, 3 }, (byte) 4)); + // short + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new short[] { 1, 2, 3 }, (short) 4)); + // int + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new int[] { 1, 2, 3 }, 4)); + // long + assertEquals(ArrayTools.NOT_FOUND_INDEX, + ArrayTools.indexOf(new long[] { 1000000L, 2000000L, 3000000L }, 4000000L)); + // float + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new float[] { 1.11F, 2.22F, 3.33F }, 4.44F)); + // double + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new double[] { 1.11, 2.22, 3.33 }, 4.44)); + } + + @Test + void indexOf_NullObject_ReturnsNotFound() { + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.indexOf(new String[] { "a", "b", "c" }, null)); + } + + @Test + void indexOf_ArrayContainsNull_ReturnsIndex() { + assertEquals(1, ArrayTools.indexOf(new String[] { "a", null, "c" }, null)); + } + + // ================================ + // #endregion + // ================================ + + // ================================ + // #region - lastIndexOf + // ================================ + + @Test + void lastIndexOfWithPredicate_NullPredicate_ThrowsNullPointerException() { + assertThrows(NullPointerException.class, () -> ArrayTools.lastIndexOfWithPredicate(new String[] {}, null)); + } + + @Test + void lastIndexOfWithPredicate_NullArray_ReturnsNotFoundIndex() { + Predicate predicate = s -> s.equals("test"); + int result = ArrayTools.lastIndexOfWithPredicate(null, predicate); + assertEquals(ArrayTools.NOT_FOUND_INDEX, result); + } + + @Test + void lastIndexOfWithPredicate_EmptyArray_ReturnsNotFoundIndex() { + Predicate predicate = s -> s.equals("test"); + int result = ArrayTools.lastIndexOfWithPredicate(new String[] {}, predicate); + assertEquals(ArrayTools.NOT_FOUND_INDEX, result); + } + + @Test + void lastIndexOfWithPredicate_ArrayContainsMatchingElement_ReturnsIndex() { + String[] array = { "apple", "banana", "banana", "cherry" }; + Predicate predicate = s -> s.equals("banana"); + int result = ArrayTools.lastIndexOfWithPredicate(array, predicate); + assertEquals(2, result); + } + + @Test + void lastIndexOfWithPredicate_ArrayDoesNotContainMatchingElement_ReturnsNotFoundIndex() { + String[] array = { "apple", "banana", "cherry" }; + Predicate predicate = s -> s.equals("orange"); + int result = ArrayTools.lastIndexOfWithPredicate(array, predicate); + assertEquals(ArrayTools.NOT_FOUND_INDEX, result); + } + + @Test + void lastIndexOf_NullArray_ReturnsNotFound() { + // T + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((String[]) null, "test")); + // char + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((char[]) null, 'a')); + // byte + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((byte[]) null, (byte) 1)); + // short + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((short[]) null, (short) 1)); + // int + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((int[]) null, 1)); + // long + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((long[]) null, 1L)); + // float + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((float[]) null, 1.23F)); + // double + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf((double[]) null, 1.23)); + } + + @Test + void lastIndexOf_EmptyArray_ReturnsNotFound() { + // T + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new String[] {}, "test")); + // char + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new char[] {}, 'a')); + // byte + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new byte[] {}, (byte) 1)); + // short + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new short[] {}, (short) 1)); + // int + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new int[] {}, 1)); + // long + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new long[] {}, 1L)); + // float + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new float[] {}, 1.23F)); + // double + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new double[] {}, 1.23)); + } + + @Test + void lastIndexOf_ObjectFound_ReturnsIndex() { + // T + assertEquals(3, ArrayTools.lastIndexOf(new String[] { "a", "b", "c", "b", "c" }, "b")); + // char + assertEquals(3, ArrayTools.lastIndexOf(new char[] { 'a', 'b', 'c', 'b', 'c' }, 'b')); + // byte + assertEquals(3, ArrayTools.lastIndexOf(new byte[] { 1, 2, 3, 2, 3 }, (byte) 2)); + // short + assertEquals(3, ArrayTools.lastIndexOf(new short[] { 1, 2, 3, 2, 3 }, (short) 2)); + // int + assertEquals(3, ArrayTools.lastIndexOf(new int[] { 1, 2, 3, 2, 3 }, 2)); + // long + assertEquals(3, + ArrayTools.lastIndexOf(new long[] { 1000000L, 2000000L, 3000000L, 2000000L, 3000000L }, 2000000L)); + // float + assertEquals(3, ArrayTools.lastIndexOf(new float[] { 1.11F, 2.22F, 3.33F, 2.22F, 3.33F }, 2.22F)); + // double + assertEquals(3, ArrayTools.lastIndexOf(new double[] { 1.11, 2.22, 3.33, 2.22, 3.33 }, 2.22)); + } + + @Test + void lastIndexOf_ObjectNotFound_ReturnsNotFound() { + // T + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new String[] { "a", "b", "c" }, "d")); + // char + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new char[] { 'a', 'b', 'c' }, 'd')); + // byte + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new byte[] { 1, 2, 3 }, (byte) 4)); + // short + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new short[] { 1, 2, 3 }, (short) 4)); + // int + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new int[] { 1, 2, 3 }, 4)); + // long + assertEquals(ArrayTools.NOT_FOUND_INDEX, + ArrayTools.lastIndexOf(new long[] { 1000000L, 2000000L, 3000000L }, 4000000L)); + // float + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new float[] { 1.11F, 2.22F, 3.33F }, 4.44F)); + // double + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new double[] { 1.11, 2.22, 3.33 }, 4.44)); + } + + @Test + void lastIndexOf_NullObject_ReturnsNotFound() { + assertEquals(ArrayTools.NOT_FOUND_INDEX, ArrayTools.lastIndexOf(new String[] { "a", "b", "c" }, null)); + } + + @Test + void lastIndexOf_ArrayContainsNull_ReturnsIndex() { + assertEquals(3, ArrayTools.lastIndexOf(new String[] { "a", null, "c", null, "e" }, null)); + } + + // ================================ + // #endregion + // ================================ + + // ================================ + // #region - contains + // ================================ + + @Test + void contains_NullArray_ReturnsFalse() { + assertFalse(ArrayTools.contains((String[]) null, "test")); + // char + assertFalse(ArrayTools.contains((char[]) null, 'a')); + // byte + assertFalse(ArrayTools.contains((byte[]) null, (byte) 1)); + // short + assertFalse(ArrayTools.contains((short[]) null, (short) 1)); + // int + assertFalse(ArrayTools.contains((int[]) null, 1)); + // long + assertFalse(ArrayTools.contains((long[]) null, 1L)); + // float + assertFalse(ArrayTools.contains((float[]) null, 1.2F)); + // double + assertFalse(ArrayTools.contains((double[]) null, 1.2)); + } + + @Test + void contains_EmptyArray_ReturnsFalse() { + assertFalse(ArrayTools.contains(new String[] {}, "test")); + // char + assertFalse(ArrayTools.contains(new char[] {}, 'a')); + // byte + assertFalse(ArrayTools.contains(new byte[] {}, (byte) 1)); + // short + assertFalse(ArrayTools.contains(new short[] {}, (short) 1)); + // int + assertFalse(ArrayTools.contains(new int[] {}, 1)); + // long + assertFalse(ArrayTools.contains(new long[] {}, 1L)); + // float + assertFalse(ArrayTools.contains(new float[] {}, 1.2F)); + // double + assertFalse(ArrayTools.contains(new double[] {}, 1.2)); + } + + @Test + void contains_ArrayContainsObject_ReturnsTrue() { + assertTrue(ArrayTools.contains(new String[] { "test", "example" }, "test")); + // char + assertTrue(ArrayTools.contains(new char[] { 'a', 'b', 'c' }, 'a')); + // byte + assertTrue(ArrayTools.contains(new byte[] { 1, 2, 3 }, (byte) 1)); + // short + assertTrue(ArrayTools.contains(new short[] { 1, 2, 3 }, (short) 1)); + // int + assertTrue(ArrayTools.contains(new int[] { 1, 2, 3 }, 1)); + // long + assertTrue(ArrayTools.contains(new long[] { 1000000L, 2000000L, 3000000L }, 1000000L)); + // float + assertTrue(ArrayTools.contains(new float[] { 1.11F, 2.22F, 3.33F }, 2.22F)); + // double + assertTrue(ArrayTools.contains(new double[] { 1.11, 2.22, 3.33 }, 2.22)); + } + + @Test + void contains_ArrayDoesNotContainObject_ReturnsFalse() { + // T + assertFalse(ArrayTools.contains(new String[] { "a", "b", "c" }, "d")); + // char + assertFalse(ArrayTools.contains(new char[] { 'a', 'b', 'c' }, 'd')); + // byte + assertFalse(ArrayTools.contains(new byte[] { 1, 2, 3 }, (byte) 4)); + // short + assertFalse(ArrayTools.contains(new short[] { 1, 2, 3 }, (short) 4)); + // int + assertFalse(ArrayTools.contains(new int[] { 1, 2, 3 }, 4)); + // long + assertFalse(ArrayTools.contains(new long[] { 1000000L, 2000000L, 3000000L }, 4000000L)); + // float + assertFalse(ArrayTools.contains(new float[] { 1.11F, 2.22F, 3.33F }, 4.44F)); + // double + assertFalse(ArrayTools.contains(new double[] { 1.11, 2.22, 3.33 }, 4.44)); + } + + @Test + void contains_ObjectIsNull_ReturnsFalse() { + assertFalse(ArrayTools.contains(new String[] { "test", "example" }, null)); + } + + @Test + void contains_ArrayContainsNull_ReturnsTrue() { + assertTrue(ArrayTools.contains(new String[] { "test", null }, null)); + } + + // ================================ + // #endregion + // ================================ + } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/AssertToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/AssertToolsTests.java new file mode 100644 index 0000000..b6ea20e --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/AssertToolsTests.java @@ -0,0 +1,942 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.util.Optional; +import java.util.function.Supplier; + +import org.junit.jupiter.api.Test; + +import xyz.zhouxy.plusone.commons.exception.DataNotExistsException; +import xyz.zhouxy.plusone.commons.exception.system.DataOperationResultException; + +@SuppressWarnings("null") +public class AssertToolsTests { + + // #region - Argument + + @Test + void testCheckArgument_true() { + AssertTools.checkArgument(true); + } + + @Test + void testCheckArgument_true_withMessage() { + final String IGNORE_ME = "IGNORE_ME"; // NOSONAR + AssertTools.checkArgument(true, IGNORE_ME); + } + + @Test + void testCheckArgument_true_withNullMessage() { + final String IGNORE_ME = null; // NOSONAR + AssertTools.checkArgument(true, IGNORE_ME); + } + + @Test + void testCheckArgument_true_withMessageSupplier() { + AssertTools.checkArgument(true, () -> "Error message: " + LocalDate.now()); + } + + @Test + void testCheckArgument_true_withNullMessageSupplier() { + final Supplier IGNORE_ME = null; // NOSONAR + AssertTools.checkArgument(true, IGNORE_ME); + } + + @Test + void testCheckArgument_true_withMessageFormat() { + LocalDate today = LocalDate.now(); + AssertTools.checkArgument(true, "String format: %s", today); + } + + @Test + void testCheckArgument_true_withNullMessageFormat() { + AssertTools.checkArgument(true, null, LocalDate.now()); + } + + @Test + void testCheckArgument_false() { + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> AssertTools.checkArgument(false)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckArgument_false_withMessage() { + final String message = "testCheckArgument_false_withMessage"; + + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> AssertTools.checkArgument(false, message)); + + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckArgument_false_withNullMessage() { + final String message = null; + + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> AssertTools.checkArgument(false, message)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckArgument_false_withMessageSupplier() { + final LocalDate today = LocalDate.now(); + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> AssertTools.checkArgument(false, () -> "Error message: " + today)); + + assertEquals("Error message: " + today, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckArgument_false_withNullMessageSupplier() { + Supplier messageSupplier = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkArgument(false, messageSupplier)); + } + + @Test + void testCheckArgument_false_withMessageFormat() { + LocalDate today = LocalDate.now(); + final IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> AssertTools.checkArgument(false, "String format: %s", today)); + assertEquals(String.format("String format: %s", today), e.getMessage()); + } + + @Test + void testCheckArgument_false_withNullMessageFormat() { + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkArgument(false, null, today)); + } + + // #endregion - Argument + + // #region - State + + @Test + void testCheckState_true() { + AssertTools.checkState(true); + } + + @Test + void testCheckState_true_withMessage() { + final String IGNORE_ME = "IGNORE_ME"; // NOSONAR + AssertTools.checkState(true, IGNORE_ME); + } + + @Test + void testCheckState_true_withNullMessage() { + final String IGNORE_ME = null; // NOSONAR + AssertTools.checkState(true, IGNORE_ME); + } + + @Test + void testCheckState_true_withMessageSupplier() { + AssertTools.checkState(true, () -> "Error message: " + LocalDate.now()); + } + + @Test + void testCheckState_true_withNullMessageSupplier() { + final Supplier IGNORE_ME = null; // NOSONAR // NOSONAR + AssertTools.checkState(true, IGNORE_ME); + } + + @Test + void testCheckState_true_withMessageFormat() { + LocalDate today = LocalDate.now(); + AssertTools.checkState(true, "String format: %s", today); + } + + @Test + void testCheckState_true_withNullMessageFormat() { + AssertTools.checkState(true, null, LocalDate.now()); + } + + @Test + void testCheckState_false() { + final IllegalStateException e = assertThrows(IllegalStateException.class, + () -> AssertTools.checkState(false)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckState_false_withMessage() { + final String message = "testCheckState_false_withMessage"; + + final IllegalStateException e = assertThrows(IllegalStateException.class, + () -> AssertTools.checkState(false, message)); + + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckState_false_withNullMessage() { + final String message = null; + + final IllegalStateException e = assertThrows(IllegalStateException.class, + () -> AssertTools.checkState(false, message)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckState_false_withMessageSupplier() { + final LocalDate today = LocalDate.now(); + final IllegalStateException e = assertThrows(IllegalStateException.class, + () -> AssertTools.checkState(false, () -> "Error message: " + today)); + + assertEquals("Error message: " + today, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckState_false_withNullMessageSupplier() { + Supplier messageSupplier = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkState(false, messageSupplier)); + } + + @Test + void testCheckState_false_withMessageFormat() { + LocalDate today = LocalDate.now(); + final IllegalStateException e = assertThrows(IllegalStateException.class, + () -> AssertTools.checkState(false, "String format: %s", today)); + assertEquals(String.format("String format: %s", today), e.getMessage()); + } + + @Test + void testCheckState_false_withNullMessageFormat() { + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkState(false, null, today)); + } + + // #endregion - State + + // #region - NotNull + + @Test + void testCheckNotNull_notNull() { + final Object object = new Object(); + AssertTools.checkNotNull(object); + } + + @Test + void testCheckNotNull_notNull_withMessage() { + final Object object = new Object(); + final String IGNORE_ME = "IGNORE_ME"; // NOSONAR + AssertTools.checkNotNull(object, IGNORE_ME); + } + + @Test + void testCheckNotNull_notNull_withNullMessage() { + final Object object = new Object(); + final String IGNORE_ME = null; // NOSONAR + AssertTools.checkNotNull(object, IGNORE_ME); + } + + @Test + void testCheckNotNull_notNull_withMessageSupplier() { + final Object object = new Object(); + AssertTools.checkNotNull(object, () -> "Error message: " + LocalDate.now()); + } + + @Test + void testCheckNotNull_notNull_withNullMessageSupplier() { + final Object object = new Object(); + final Supplier IGNORE_ME = null; // NOSONAR // NOSONAR + AssertTools.checkNotNull(object, IGNORE_ME); + } + + @Test + void testCheckNotNull_notNull_withMessageFormat() { + final Object object = new Object(); + LocalDate today = LocalDate.now(); + AssertTools.checkNotNull(object, "String format: %s", today); + } + + @Test + void testCheckNotNull_notNull_withNullMessageFormat() { + final Object object = new Object(); + AssertTools.checkNotNull(object, null, LocalDate.now()); + } + + @Test + void testCheckNotNull_null() { + final Object object = null; + final NullPointerException e = assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckNotNull_null_withMessage() { + final Object object = null; + final String message = "testCheckNotNull_null_withMessage"; + + final NullPointerException e = assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object, message)); + + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckNotNull_null_withNullMessage() { + final Object object = null; + final String message = null; + + final NullPointerException e = assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object, message)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckNotNull_null_withMessageSupplier() { + final Object object = null; + final LocalDate today = LocalDate.now(); + final NullPointerException e = assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object, () -> "Error message: " + today)); + + assertEquals("Error message: " + today, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckNotNull_null_withNullMessageSupplier() { + final Object object = null; + Supplier messageSupplier = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object, messageSupplier)); + } + + @Test + void testCheckNotNull_null_withMessageFormat() { + final Object object = null; + LocalDate today = LocalDate.now(); + final NullPointerException e = assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object, "String format: %s", today)); + assertEquals(String.format("String format: %s", today), e.getMessage()); + } + + @Test + void testCheckNotNull_null_withNullMessageFormat() { + final Object object = null; + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkNotNull(object, null, today)); + } + + // #endregion - NotNull + + // #region - Exists + + @Test + void testCheckExists_notNull() throws DataNotExistsException { + final Object object = new Object(); + AssertTools.checkExists(object); + } + + @Test + void testCheckExists_notNull_withMessage() throws DataNotExistsException { + final Object object = new Object(); + final String IGNORE_ME = "IGNORE_ME"; // NOSONAR + AssertTools.checkExists(object, IGNORE_ME); + } + + @Test + void testCheckExists_notNull_withNullMessage() throws DataNotExistsException { + final Object object = new Object(); + final String IGNORE_ME = null; // NOSONAR + AssertTools.checkExists(object, IGNORE_ME); + } + + @Test + void testCheckExists_notNull_withMessageSupplier() throws DataNotExistsException { + final Object object = new Object(); + AssertTools.checkExists(object, () -> "Error message: " + LocalDate.now()); + } + + @Test + void testCheckExists_notNull_withNullMessageSupplier() throws DataNotExistsException { + final Object object = new Object(); + final Supplier IGNORE_ME = null; // NOSONAR // NOSONAR + AssertTools.checkExists(object, IGNORE_ME); + } + + @Test + void testCheckExists_notNull_withMessageFormat() throws DataNotExistsException { + final Object object = new Object(); + LocalDate today = LocalDate.now(); + AssertTools.checkExists(object, "String format: %s", today); + } + + @Test + void testCheckExists_notNull_withNullMessageFormat() throws DataNotExistsException { + final Object object = new Object(); + AssertTools.checkExists(object, null, LocalDate.now()); + } + + @Test + void testCheckExists_null() { + final Object object = null; + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_null_withMessage() { + final Object object = null; + final String message = "testCheckExists_null_withMessage"; + + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, message)); + + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_null_withNullMessage() { + final Object object = null; + final String message = null; + + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, message)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_null_withMessageSupplier() { + final Object object = null; + final LocalDate today = LocalDate.now(); + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, () -> "Error message: " + today)); + + assertEquals("Error message: " + today, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_null_withNullMessageSupplier() { + final Object object = null; + Supplier messageSupplier = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, messageSupplier)); + } + + @Test + void testCheckExists_null_withMessageFormat() { + final Object object = null; + LocalDate today = LocalDate.now(); + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, "String format: %s", today)); + assertEquals(String.format("String format: %s", today), e.getMessage()); + } + + @Test + void testCheckExists_null_withNullMessageFormat() { + final Object object = null; + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, null, today)); + } + + @Test + void testCheckExists_optional() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + AssertTools.checkExists(object); + } + + @Test + void testCheckExists_optional_withMessage() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + final String IGNORE_ME = "IGNORE_ME"; // NOSONAR + AssertTools.checkExists(object, IGNORE_ME); + } + + @Test + void testCheckExists_optional_withNullMessage() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + final String IGNORE_ME = null; // NOSONAR + AssertTools.checkExists(object, IGNORE_ME); + } + + @Test + void testCheckExists_optional_withMessageSupplier() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + AssertTools.checkExists(object, () -> "Error message: " + LocalDate.now()); + } + + @Test + void testCheckExists_optional_withNullMessageSupplier() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + final Supplier IGNORE_ME = null; // NOSONAR + AssertTools.checkExists(object, IGNORE_ME); + } + + @Test + void testCheckExists_optional_withMessageFormat() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + LocalDate today = LocalDate.now(); + AssertTools.checkExists(object, "String format: %s", today); + } + + @Test + void testCheckExists_optional_withNullMessageFormat() throws DataNotExistsException { + final Optional object = Optional.of(new Object()); + AssertTools.checkExists(object, null, LocalDate.now()); + } + + @Test + void testCheckExists_emptyOptional() { + final Optional object = Optional.empty(); + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_emptyOptional_withMessage() { + final Optional object = Optional.empty(); + final String message = "testCheckExists_emptyOptional_withMessage"; + + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, message)); + + assertEquals(message, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_emptyOptional_withNullMessage() { + final Optional object = Optional.empty(); + final String message = null; + + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, message)); + + assertNull(e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_emptyOptional_withMessageSupplier() { + final Optional object = Optional.empty(); + final LocalDate today = LocalDate.now(); + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, () -> "Error message: " + today)); + + assertEquals("Error message: " + today, e.getMessage()); + assertNull(e.getCause()); + } + + @Test + void testCheckExists_emptyOptional_withNullMessageSupplier() { + final Optional object = Optional.empty(); + Supplier messageSupplier = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, messageSupplier)); + } + + @Test + void testCheckExists_emptyOptional_withMessageFormat() { + final Optional object = Optional.empty(); + LocalDate today = LocalDate.now(); + final DataNotExistsException e = assertThrows(DataNotExistsException.class, + () -> AssertTools.checkExists(object, "String format: %s", today)); + assertEquals(String.format("String format: %s", today), e.getMessage()); + } + + @Test + void testCheckExists_emptyOptional_withNullMessageFormat() { + final Optional object = Optional.empty(); + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, null, today)); + } + + @Test + void testCheckExists_nullOptional() { + final Optional object = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object)); + } + + @Test + void testCheckExists_nullOptional_withMessage() { + final Optional object = null; + final String message = "testCheckExists_nullOptional_withMessage"; + + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, message)); + } + + @Test + void testCheckExists_nullOptional_withNullMessage() { + final Optional object = null; + final String message = null; + + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, message)); + } + + @Test + void testCheckExists_nullOptional_withMessageSupplier() { + final Optional object = null; + final LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, () -> "Error message: " + today)); + } + + @Test + void testCheckExists_nullOptional_withNullMessageSupplier() { + final Optional object = null; + Supplier messageSupplier = null; + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, messageSupplier)); + } + + @Test + void testCheckExists_nullOptional_withMessageFormat() { + final Optional object = null; + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, "String format: %s", today)); + } + + @Test + void testCheckExists_nullOptional_withNullMessageFormat() { + final Optional object = null; + LocalDate today = LocalDate.now(); + assertThrows(NullPointerException.class, + () -> AssertTools.checkExists(object, null, today)); + } + + // #endregion - Exists + + // #region - AffectedRows + + @Test + void testCheckAffectedRows_int() { + final int expectedValue = 25; + + AssertTools.checkAffectedRows(expectedValue, 25); + + DataOperationResultException e0 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108)); + assertEquals(String.format("The number of rows affected is expected to be %d, but is: %d", expectedValue, 108), + e0.getMessage()); + + } + + @Test + void testCheckAffectedRows_int_message() { + final int expectedValue = 25; + + final String message = "Static message"; + + AssertTools.checkAffectedRows(expectedValue, 25, message); + + DataOperationResultException e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108, message)); + assertEquals(message, e1.getMessage()); + + final String nullMessage = null; + + AssertTools.checkAffectedRows(expectedValue, 25, nullMessage); + + e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108, nullMessage)); + assertNull(e1.getMessage()); + } + + @Test + void testCheckAffectedRows_int_messageSupplier() { + final int expectedValue = 25; + + final Supplier messageSupplier = () -> "Supplier message"; + + AssertTools.checkAffectedRows(expectedValue, 25, messageSupplier); + + DataOperationResultException e2 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108, messageSupplier)); + assertEquals(messageSupplier.get(), e2.getMessage()); + + final Supplier nullSupplier = null; + + AssertTools.checkAffectedRows(expectedValue, 25, nullSupplier); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108, nullSupplier)); + } + + @Test + void testCheckAffectedRows_int_messageFormat() { + final int expectedValue = 25; + + AssertTools.checkAffectedRows(expectedValue, 25, "预计是 %d,结果是 %d。", expectedValue, 25); + + DataOperationResultException e3 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108, "预计是 %d,结果是 %d。", expectedValue, 108)); + assertEquals("预计是 25,结果是 108。", e3.getMessage()); + + AssertTools.checkAffectedRows(expectedValue, 25, null, expectedValue, 25); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108, null, expectedValue, 108)); + } + + @Test + void testCheckAffectedRows_long() { + final long expectedValue = 25L; + + AssertTools.checkAffectedRows(expectedValue, 25L); + + DataOperationResultException e0 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L)); + assertEquals(String.format("The number of rows affected is expected to be %d, but is: %d", expectedValue, 108L), + e0.getMessage()); + + } + + @Test + void testCheckAffectedRows_long_message() { + final long expectedValue = 25L; + + final String message = "Static message"; + + AssertTools.checkAffectedRows(expectedValue, 25L, message); + + DataOperationResultException e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L, message)); + assertEquals(message, e1.getMessage()); + + final String nullMessage = null; + + AssertTools.checkAffectedRows(expectedValue, 25L, nullMessage); + + e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L, nullMessage)); + assertNull(e1.getMessage()); + } + + @Test + void testCheckAffectedRows_long_messageSupplier() { + final long expectedValue = 25L; + + final Supplier messageSupplier = () -> "Supplier message"; + + AssertTools.checkAffectedRows(expectedValue, 25L, messageSupplier); + + DataOperationResultException e2 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L, messageSupplier)); + assertEquals(messageSupplier.get(), e2.getMessage()); + + final Supplier nullSupplier = null; + + AssertTools.checkAffectedRows(expectedValue, 25L, nullSupplier); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L, nullSupplier)); + } + + @Test + void testCheckAffectedRows_long_messageFormat() { + final long expectedValue = 25L; + + AssertTools.checkAffectedRows(expectedValue, 25L, "预计是 %d,结果是 %d。", expectedValue, 25L); + + DataOperationResultException e3 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L, "预计是 %d,结果是 %d。", expectedValue, 108L)); + assertEquals("预计是 25,结果是 108。", e3.getMessage()); + + AssertTools.checkAffectedRows(expectedValue, 25L, null, expectedValue, 25L); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedRows(expectedValue, 108L, null, expectedValue, 108L)); + } + + @Test + void testCheckAffectedOneRow_int() { + AssertTools.checkAffectedOneRow(1); + + DataOperationResultException e0 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108)); + assertEquals(String.format("The number of rows affected is expected to be 1, but is: %d", 108), + e0.getMessage()); + + } + + @Test + void testCheckAffectedOneRow_int_message() { + final String message = "Static message"; + + AssertTools.checkAffectedOneRow(1, message); + + DataOperationResultException e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108, message)); + assertEquals(message, e1.getMessage()); + + final String nullMessage = null; + + AssertTools.checkAffectedOneRow(1, nullMessage); + + e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108, nullMessage)); + assertNull(e1.getMessage()); + } + + @Test + void testCheckAffectedOneRow_int_messageSupplier() { + final Supplier messageSupplier = () -> "Supplier message"; + + AssertTools.checkAffectedOneRow(1, messageSupplier); + + DataOperationResultException e2 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108, messageSupplier)); + assertEquals(messageSupplier.get(), e2.getMessage()); + + final Supplier nullSupplier = null; + + AssertTools.checkAffectedOneRow(1, nullSupplier); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedOneRow(108, nullSupplier)); + } + + @Test + void testCheckAffectedOneRow_int_messageFormat() { + AssertTools.checkAffectedOneRow(1, "预计是 %d,结果是 %d。", 1, 108); + + DataOperationResultException e3 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108, "预计是 %d,结果是 %d。", 1, 108)); + assertEquals("预计是 1,结果是 108。", e3.getMessage()); + + AssertTools.checkAffectedOneRow(1, null, 1); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedOneRow(108, null, 108)); + } + + @Test + void testCheckAffectedOneRow_long() { + AssertTools.checkAffectedOneRow(1L); + + DataOperationResultException e0 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108L)); + assertEquals(String.format("The number of rows affected is expected to be 1, but is: %d", 108L), + e0.getMessage()); + + } + + @Test + void testCheckAffectedOneRow_long_message() { + final String message = "Static message"; + + AssertTools.checkAffectedOneRow(1L, message); + + DataOperationResultException e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108L, message)); + assertEquals(message, e1.getMessage()); + + final String nullMessage = null; + + AssertTools.checkAffectedOneRow(1L, nullMessage); + + e1 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108L, nullMessage)); + assertNull(e1.getMessage()); + } + + @Test + void testCheckAffectedOneRow_long_messageSupplier() { + final Supplier messageSupplier = () -> "Supplier message"; + + AssertTools.checkAffectedOneRow(1L, messageSupplier); + + DataOperationResultException e2 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108L, messageSupplier)); + assertEquals(messageSupplier.get(), e2.getMessage()); + + final Supplier nullSupplier = null; + + AssertTools.checkAffectedOneRow(1L, nullSupplier); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedOneRow(108L, nullSupplier)); + } + + @Test + void testCheckAffectedOneRow_long_messageFormat() { + AssertTools.checkAffectedOneRow(1L, "预计是 %d,结果是 %d。", 1L, 108L); + + DataOperationResultException e3 = assertThrows(DataOperationResultException.class, + () -> AssertTools.checkAffectedOneRow(108L, "预计是 %d,结果是 %d。", 1L, 108L)); + assertEquals("预计是 1,结果是 108。", e3.getMessage()); + + AssertTools.checkAffectedOneRow(1L, null, 1L); + + assertThrows(NullPointerException.class, + () -> AssertTools.checkAffectedOneRow(108L, null, 108L)); + } + + // #endregion - AffectedRows + + // #region - Condition + + static final class MyException extends RuntimeException {} + + @Test + void testCheckCondition() { + + AssertTools.checkCondition(true, MyException::new); + + final MyException me = new MyException(); + MyException e = assertThrows(MyException.class, () -> AssertTools.checkCondition(false, () -> me)); + assertEquals(me, e); + } + + // #endregion - Condition + +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/BigDecimalsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/BigDecimalsTests.java index 21f25c6..2fc6a0c 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/BigDecimalsTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/BigDecimalsTests.java @@ -16,11 +16,9 @@ package xyz.zhouxy.plusone.commons.util; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import java.math.BigDecimal; -import java.math.RoundingMode; -import java.util.function.Function; import org.junit.jupiter.api.Test; @@ -30,75 +28,178 @@ import lombok.extern.slf4j.Slf4j; public class BigDecimalsTests { @Test - void testToPlainString() { - - BigDecimalFormatter formatter = BigDecimalFormatter.builder() - .setScale(2, RoundingMode.HALF_UP) - .stripTrailingZeros() - .build(); - - assertEquals("8.09", formatter.toPlainString(BigDecimals.of("8.090"))); - assertEquals("8.09", formatter.toPlainString(BigDecimals.of("8.094"))); - assertEquals("8.1", formatter.toPlainString(BigDecimals.of("8.095"))); - assertEquals("8.1", formatter.toPlainString(BigDecimals.of("8.096"))); - assertEquals("8.1", formatter.toPlainString(BigDecimals.of("8.100"))); + void equalsValue_NullValues_ReturnsTrue() { + assertTrue(BigDecimals.equalsValue(null, null)); } @Test - void test() { - Object a = 100 % 3.0; - log.info("a: {}", a); - } -} - -class BigDecimalFormatter { - private final Function func; - - private BigDecimalFormatter(Function wholeFunc) { - this.func = wholeFunc; - } - - public static Builder builder() { - return new Builder(); - } - - public String toPlainString(BigDecimal value) { - final BigDecimal finalDecimal = func == null ? value : func.apply(value); - return finalDecimal.toPlainString(); - } - - public String toEngineeringString(BigDecimal value) { - final BigDecimal finalDecimal = func == null ? value : func.apply(value); - return finalDecimal.toEngineeringString(); - } - - public static class Builder { - private Function wholeFunc; - - private Builder() { - } - - public Builder setScale(int newScale, RoundingMode roundingMode) { - final Function func = value -> value.setScale(newScale, roundingMode); - if (wholeFunc == null) { - wholeFunc = func; - } else { - wholeFunc = func.andThen(func); - } - return this; - } - - public Builder stripTrailingZeros() { - if (wholeFunc == null) { - wholeFunc = BigDecimal::stripTrailingZeros; - } else { - wholeFunc = wholeFunc.andThen(BigDecimal::stripTrailingZeros); - } - return this; - } - - public BigDecimalFormatter build() { - return new BigDecimalFormatter(wholeFunc); - } + void equalsValue_SameNonNullableValues_ReturnsTrue() { + BigDecimal bd1 = new BigDecimal("10"); + BigDecimal bd2 = new BigDecimal("10.0"); + assertTrue(BigDecimals.equalsValue(bd1, bd2)); } + + @Test + void equalsValue_DifferentNonNullableValues_ReturnsFalse() { + BigDecimal bd1 = new BigDecimal("10"); + BigDecimal bd2 = new BigDecimal("20"); + assertFalse(BigDecimals.equalsValue(bd1, bd2)); + } + + @Test + void equalsValue_OneNullOneNonNullValue_ReturnsFalse() { + BigDecimal bd = new BigDecimal("10"); + assertFalse(BigDecimals.equalsValue(bd, null)); + assertFalse(BigDecimals.equalsValue(null, bd)); + } + + @Test + void gt_NullFirstValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.gt(null, bd)); + } + + @Test + void gt_NullSecondValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.gt(bd, null)); + } + + @Test + void gt_SameValues_ReturnsFalse() { + BigDecimal bd = new BigDecimal("10"); + assertFalse(BigDecimals.gt(bd, bd)); + } + + @Test + void gt_FirstGreaterThanSecond_ReturnsTrue() { + BigDecimal bd1 = new BigDecimal("20"); + BigDecimal bd2 = new BigDecimal("10"); + assertTrue(BigDecimals.gt(bd1, bd2)); + } + + @Test + void ge_NullFirstValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.ge(null, bd)); + } + + @Test + void ge_NullSecondValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.ge(bd, null)); + } + + @Test + void ge_SameValues_ReturnsTrue() { + BigDecimal bd = new BigDecimal("10"); + assertTrue(BigDecimals.ge(bd, bd)); + } + + @Test + void ge_FirstGreaterThanSecond_ReturnsTrue() { + BigDecimal bd1 = new BigDecimal("20"); + BigDecimal bd2 = new BigDecimal("10"); + assertTrue(BigDecimals.ge(bd1, bd2)); + } + + @Test + void lt_NullFirstValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.lt(null, bd)); + } + + @Test + void lt_NullSecondValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.lt(bd, null)); + } + + @Test + void lt_SameValues_ReturnsFalse() { + BigDecimal bd = new BigDecimal("10"); + assertFalse(BigDecimals.lt(bd, bd)); + } + + @Test + void lt_FirstLessThanSecond_ReturnsTrue() { + BigDecimal bd1 = new BigDecimal("10"); + BigDecimal bd2 = new BigDecimal("20"); + assertTrue(BigDecimals.lt(bd1, bd2)); + } + + @Test + void le_NullFirstValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.le(null, bd)); + } + + @Test + void le_NullSecondValue_ThrowsException() { + BigDecimal bd = new BigDecimal("10"); + assertThrows(NullPointerException.class, () -> BigDecimals.le(bd, null)); + } + + @Test + void le_SameValues_ReturnsTrue() { + BigDecimal bd = new BigDecimal("10"); + assertTrue(BigDecimals.le(bd, bd)); + } + + @Test + void le_FirstLessThanSecond_ReturnsTrue() { + BigDecimal bd1 = new BigDecimal("10"); + BigDecimal bd2 = new BigDecimal("20"); + assertTrue(BigDecimals.le(bd1, bd2)); + } + + @Test + void sum_NullArray_ReturnsZero() { + assertEquals(BigDecimal.ZERO, BigDecimals.sum()); + } + + @Test + void sum_SingleNonNullValue_ReturnsSameValue() { + BigDecimal bd = new BigDecimal("10"); + assertEquals(bd, BigDecimals.sum(bd)); + } + + @Test + void sum_SingleNullValue_ReturnsZero() { + assertEquals(BigDecimal.ZERO, BigDecimals.sum((BigDecimal) null)); + } + + @Test + void sum_MultipleValues_ReturnsCorrectSum() { + BigDecimal bd1 = new BigDecimal("10"); + BigDecimal bd2 = new BigDecimal("20"); + BigDecimal bd3 = new BigDecimal("30"); + BigDecimal bd4 = null; + assertEquals(new BigDecimal("60"), BigDecimals.sum(bd1, bd2, bd3, bd4)); + } + + @Test + void nullToZero_NullValue_ReturnsZero() { + assertEquals(BigDecimal.ZERO, BigDecimals.nullToZero(null)); + } + + @Test + void nullToZero_NonNullValue_ReturnsSameValue() { + BigDecimal bd = new BigDecimal("10"); + assertEquals(bd, BigDecimals.nullToZero(bd)); + } + + @Test + void of_BlankString_ReturnsZero() { + assertEquals(BigDecimal.ZERO, BigDecimals.of(null)); + assertEquals(BigDecimal.ZERO, BigDecimals.of("")); + assertEquals(BigDecimal.ZERO, BigDecimals.of(" ")); + } + + @Test + void of_NonBlankString_ReturnsCorrectBigDecimal() { + BigDecimal bd = new BigDecimal("10"); + assertEquals(bd, BigDecimals.of("10")); + } + } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java index 28708a7..a1796bf 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/DateTimeToolsTests.java @@ -17,94 +17,480 @@ package xyz.zhouxy.plusone.commons.util; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.time.DateTimeException; import java.time.Instant; +import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Month; +import java.time.Year; +import java.time.YearMonth; import java.time.ZoneId; import java.time.ZonedDateTime; -import java.time.format.DateTimeFormatter; +import java.util.Calendar; +import java.util.Date; +import java.util.TimeZone; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Range; + +import xyz.zhouxy.plusone.commons.time.Quarter; +import xyz.zhouxy.plusone.commons.time.YearQuarter; + class DateTimeToolsTests { private static final Logger log = LoggerFactory.getLogger(DateTimeToolsTests.class); + // Java + static final LocalDateTime LOCAL_DATE_TIME = LocalDateTime.of(2024, 12, 29, 12, 58, 30, 333000000); + static final LocalDate LOCAL_DATE = LOCAL_DATE_TIME.toLocalDate(); + static final LocalTime LOCAL_TIME = LOCAL_DATE_TIME.toLocalTime(); + + // Java - 2024-12-29 12:58:30.333333333 SystemDefaultZone + static final ZoneId SYS_ZONE_ID = ZoneId.systemDefault(); + static final ZonedDateTime ZONED_DATE_TIME_WITH_SYS_ZONE = LOCAL_DATE_TIME.atZone(SYS_ZONE_ID); + static final Instant INSTANT_WITH_SYS_ZONE = ZONED_DATE_TIME_WITH_SYS_ZONE.toInstant(); + static final long INSTANT_MILLIS = INSTANT_WITH_SYS_ZONE.toEpochMilli(); + + static final TimeZone SYS_TIME_ZONE = TimeZone.getDefault(); + static final Date SYS_DATE = Date.from(INSTANT_WITH_SYS_ZONE); + static final Calendar SYS_CALENDAR = Calendar.getInstance(SYS_TIME_ZONE); + static { + SYS_CALENDAR.setTime(SYS_DATE); + } + + // Java - 2024-12-29 12:58:30.333333333 GMT+04:00 + static final ZoneId ZONE_ID = ZoneId.of("GMT+04:00"); + static final ZonedDateTime ZONED_DATE_TIME = LOCAL_DATE_TIME.atZone(ZONE_ID); + static final Instant INSTANT = ZONED_DATE_TIME.toInstant(); + static final long MILLIS = INSTANT.toEpochMilli(); + + static final TimeZone TIME_ZONE = TimeZone.getTimeZone(ZONE_ID); + static final Date DATE = Date.from(INSTANT); + static final Calendar CALENDAR = Calendar.getInstance(TIME_ZONE); + static { + CALENDAR.setTime(DATE); + } + + // Joda + static final org.joda.time.LocalDateTime JODA_LOCAL_DATE_TIME + = new org.joda.time.LocalDateTime(2024, 12, 29, 12, 58, 30, 333); + static final org.joda.time.LocalDate JODA_LOCAL_DATE = JODA_LOCAL_DATE_TIME.toLocalDate(); + static final org.joda.time.LocalTime JODA_LOCAL_TIME = JODA_LOCAL_DATE_TIME.toLocalTime(); + + // Joda - 2024-12-29 12:58:30.333 SystemDefaultZone + static final org.joda.time.DateTimeZone JODA_SYS_ZONE = org.joda.time.DateTimeZone.getDefault(); + static final org.joda.time.DateTime JODA_DATE_TIME_WITH_SYS_ZONE = JODA_LOCAL_DATE_TIME.toDateTime(JODA_SYS_ZONE); + static final org.joda.time.Instant JODA_INSTANT_WITH_SYS_ZONE = JODA_DATE_TIME_WITH_SYS_ZONE.toInstant(); + static final long JODA_INSTANT_MILLIS = JODA_INSTANT_WITH_SYS_ZONE.getMillis(); + + // Joda - 2024-12-29 12:58:30.333 GMT+04:00 + static final org.joda.time.DateTimeZone JODA_ZONE = org.joda.time.DateTimeZone.forID("GMT+04:00"); + static final org.joda.time.DateTime JODA_DATE_TIME = JODA_LOCAL_DATE_TIME.toDateTime(JODA_ZONE); + static final org.joda.time.Instant JODA_INSTANT = JODA_DATE_TIME.toInstant(); + static final long JODA_MILLIS = JODA_INSTANT.getMillis(); + + // ================================ + // #region - toDate + // ================================ + @Test - void testLocalNowStr() { - String nowStr = DateTimeTools.nowStr("yyyy/MM/dd HH:mm:ss.SSS"); - log.info(nowStr); - assertEquals(23, nowStr.length()); + void toDate_timeMillis() { + assertNotEquals(SYS_DATE, DATE); + log.info("SYS_DATE: {}, DATE: {}", SYS_DATE, DATE); + assertEquals(SYS_DATE, JODA_DATE_TIME_WITH_SYS_ZONE.toDate()); + assertEquals(SYS_DATE, DateTimeTools.toDate(INSTANT_MILLIS)); + assertEquals(DATE, JODA_DATE_TIME.toDate()); + assertEquals(DATE, DateTimeTools.toDate(MILLIS)); } @Test - void testToJoda() { - LocalDateTime dt = LocalDateTime.of(2008, 8, 8, 20, 18, 59, 108000000); - log.info("src: {}", dt); - org.joda.time.LocalDateTime dt2 = DateTimeTools.toJodaLocalDateTime(dt); - log.info("result: {}", dt2); - org.joda.time.format.DateTimeFormatter f = org.joda.time.format.DateTimeFormat - .forPattern("yyyy-MM-dd HH:mm:ss.SSS"); - - assertEquals("2008-08-08 20:18:59.108", f.print(dt2)); + void toDate_calendar() { + assertEquals(SYS_DATE, DateTimeTools.toDate(SYS_CALENDAR)); + assertEquals(DATE, DateTimeTools.toDate(CALENDAR)); } @Test - void testToInstant() { - ZonedDateTime dt = ZonedDateTime.of(2008, 1, 8, 10, 23, 50, 108000000, ZoneId.systemDefault()); - Instant instant = DateTimeTools.toInstant(dt.toInstant().toEpochMilli()); - String str = DateTimeTools.toString("yy-M-d HH:mm:ss.SSS", instant); - log.info(str); - assertEquals("08-1-8 10:23:50.108", str); + void toDate_instant() { + assertEquals(SYS_DATE, DateTimeTools.toDate(INSTANT_WITH_SYS_ZONE)); + assertEquals(DATE, DateTimeTools.toDate(INSTANT)); } @Test - void testToJodaDateTime() { - ZonedDateTime dt = ZonedDateTime.of(2008, 1, 8, 10, 23, 50, 108000000, ZoneId.systemDefault()); - Instant instant = DateTimeTools.toInstant(dt.toInstant().toEpochMilli()); - - org.joda.time.format.DateTimeFormatter f = org.joda.time.format.DateTimeFormat - .forPattern("yyyy-MM-dd HH:mm:ss.SSS"); - - org.joda.time.DateTime jodaDateTime = DateTimeTools.toJodaDateTime(instant, ZoneId.of("+08:00")); - log.info("jodaDateTime: {}", jodaDateTime); - assertEquals("2008-01-08 10:23:50.108", f.print(jodaDateTime)); - - jodaDateTime = DateTimeTools.toJodaDateTime(instant, ZoneId.of("+02:00")); - log.info("jodaDateTime: {}", jodaDateTime); - assertEquals("2008-01-08 04:23:50.108", f.print(jodaDateTime)); + void toDate_ZoneDateTime() { + assertEquals(SYS_DATE, DateTimeTools.toDate(ZONED_DATE_TIME_WITH_SYS_ZONE)); + assertEquals(DATE, DateTimeTools.toDate(ZONED_DATE_TIME)); } @Test - void test() { - java.time.Instant now = java.time.Instant.now(); - org.joda.time.DateTime jodaDateTime = DateTimeTools.toJodaDateTime(now, ZoneId.of("America/New_York")); - org.joda.time.format.DateTimeFormatter formatter = org.joda.time.format.DateTimeFormat - .forPattern("yyyy-MM-dd HH:mm:ss.SSS"); - log.info(formatter.print(jodaDateTime)); - log.info(jodaDateTime.getZone().toString()); - log.info(jodaDateTime.toString()); - log.info("=========================================="); - org.joda.time.Instant instant = new org.joda.time.Instant(System.currentTimeMillis() - 500000); - log.info(instant.toString()); - log.info(DateTimeTools.toJavaInstant(instant).toString()); - log.info(DateTimeTools.toZonedDateTime(instant, org.joda.time.DateTimeZone.forID("America/New_York")) - .toString()); + void toDate_LocalDateTimeAndZoneId() { + assertEquals(SYS_DATE, DateTimeTools.toDate(LOCAL_DATE_TIME, SYS_ZONE_ID)); + assertEquals(DATE, DateTimeTools.toDate(LOCAL_DATE_TIME, ZONE_ID)); + assertEquals(SYS_DATE, DateTimeTools.toDate(LOCAL_DATE, LOCAL_TIME, SYS_ZONE_ID)); + assertEquals(DATE, DateTimeTools.toDate(LOCAL_DATE, LOCAL_TIME, ZONE_ID)); + } + + // ================================ + // #endregion - toDate + // ================================ + + // ================================ + // #region - toInstant + // ================================ + + @Test + void toInstant_timeMillis() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toInstant(INSTANT_MILLIS)); + assertEquals(INSTANT, DateTimeTools.toInstant(MILLIS)); } @Test - void testToJodaInstant() { - java.time.Instant javaInstant = java.time.Instant.now(); - log.info("javaInstant: {}", javaInstant); - - org.joda.time.Instant jodaInstant = DateTimeTools.toJodaInstant(javaInstant); - log.info("jodaInstant: {}", jodaInstant); - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); - DateTimeFormatter formatter2 = formatter.withZone(ZoneId.systemDefault()); - log.info("{}", formatter); - log.info("{}", formatter2); + void toInstant_Date() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toInstant(SYS_DATE)); + assertEquals(INSTANT, DateTimeTools.toInstant(DATE)); } + + @Test + void toInstant_Calendar() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toInstant(SYS_CALENDAR)); + assertEquals(INSTANT, DateTimeTools.toInstant(CALENDAR)); + } + + @Test + void toInstant_ZonedDateTime() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toInstant(ZONED_DATE_TIME_WITH_SYS_ZONE)); + assertEquals(INSTANT, DateTimeTools.toInstant(ZONED_DATE_TIME)); + } + + @Test + void toInstant_LocalDateTimeAndZone() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toInstant(LOCAL_DATE_TIME, SYS_ZONE_ID)); + assertEquals(INSTANT, DateTimeTools.toInstant(LOCAL_DATE_TIME, ZONE_ID)); + } + + // ================================ + // #endregion - toInstant + // ================================ + + // ================================ + // #region - toZonedDateTime + // ================================ + + @Test + void toZonedDateTime_TimeMillisAndZone() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(INSTANT_MILLIS, SYS_ZONE_ID)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(MILLIS, ZONE_ID)); + } + + @Test + void toZonedDateTime_DateAndZoneId() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(SYS_DATE, SYS_ZONE_ID)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(DATE, ZONE_ID)); + } + + @Test + void toZonedDateTime_DateAndTimeZone() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(SYS_DATE, SYS_TIME_ZONE)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(DATE, TIME_ZONE)); + } + + @Test + void toZonedDateTime_Calendar() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(SYS_CALENDAR)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(CALENDAR)); + } + + @Test + void toZonedDateTime_CalendarAndZoneId() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(SYS_CALENDAR, SYS_ZONE_ID)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(CALENDAR, ZONE_ID)); + } + + @Test + void toZonedDateTime_CalendarAndTimeZone() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(SYS_CALENDAR, SYS_TIME_ZONE)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(CALENDAR, TIME_ZONE)); + } + + @Test + void toZonedDateTime_LocalDateTimeAndZoneId() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(LOCAL_DATE_TIME, SYS_ZONE_ID)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(LOCAL_DATE_TIME, ZONE_ID)); + } + + // ================================ + // #endregion - toZonedDateTime + // ================================ + + // ================================ + // #region - toLocalDateTime + // ================================ + + @Test + void toLocalDateTime_TimeMillisAndZoneId() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(INSTANT_MILLIS, SYS_ZONE_ID)); + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(MILLIS, ZONE_ID)); + } + + @Test + void toLocalDateTime_DateAndZoneId() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(SYS_DATE, SYS_ZONE_ID)); + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(DATE, ZONE_ID)); + } + + @Test + void toLocalDateTime_DateAndTimeZone() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(SYS_DATE, SYS_TIME_ZONE)); + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(DATE, TIME_ZONE)); + } + + @Test + void toLocalDateTime_CalendarAndZoneId() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(SYS_CALENDAR, SYS_ZONE_ID)); + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(CALENDAR, ZONE_ID)); + } + + @Test + void toLocalDateTime_CalendarAndTimeZone() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(SYS_CALENDAR, SYS_TIME_ZONE)); + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(CALENDAR, TIME_ZONE)); + } + + @Test + void toLocalDateTime_ZonedDateTimeAndZoneId() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(ZONED_DATE_TIME_WITH_SYS_ZONE, SYS_ZONE_ID)); + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toLocalDateTime(ZONED_DATE_TIME, ZONE_ID)); + } + + // ================================ + // #endregion - toLocalDateTime + // ================================ + + // ================================ + // #region - toJodaInstant + // ================================ + + @Test + void toJodaInstant_JavaInstant() { + assertEquals(JODA_INSTANT_WITH_SYS_ZONE, DateTimeTools.toJodaInstant(INSTANT_WITH_SYS_ZONE)); + assertEquals(JODA_INSTANT, DateTimeTools.toJodaInstant(INSTANT)); + } + + @Test + void toJodaInstant_ZonedDateTime() { + assertEquals(JODA_INSTANT_WITH_SYS_ZONE, DateTimeTools.toJodaInstant(ZONED_DATE_TIME_WITH_SYS_ZONE)); + assertEquals(JODA_INSTANT, DateTimeTools.toJodaInstant(ZONED_DATE_TIME)); + } + + @Test + void toJodaInstant_LocalDateTimeAndZoneId() { + assertEquals(JODA_INSTANT_WITH_SYS_ZONE, DateTimeTools.toJodaInstant(LOCAL_DATE_TIME, SYS_ZONE_ID)); + assertEquals(JODA_INSTANT, DateTimeTools.toJodaInstant(LOCAL_DATE_TIME, ZONE_ID)); + } + + // ================================ + // #endregion - toJodaInstant + // ================================ + + // ================================ + // #region - toJavaInstant + // ================================ + + @Test + void toJavaInstant_JodaInstant() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toJavaInstant(JODA_INSTANT_WITH_SYS_ZONE)); + assertEquals(INSTANT, DateTimeTools.toJavaInstant(JODA_INSTANT)); + } + + @Test + void toJavaInstant_JodaDateTime() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toJavaInstant(JODA_DATE_TIME_WITH_SYS_ZONE)); + assertEquals(INSTANT, DateTimeTools.toJavaInstant(JODA_DATE_TIME)); + } + + @Test + void toJavaInstant_JodaLocalDateTimeAndJodaDateTimeZone() { + assertEquals(INSTANT_WITH_SYS_ZONE, DateTimeTools.toJavaInstant(JODA_LOCAL_DATE_TIME, JODA_SYS_ZONE)); + assertEquals(INSTANT, DateTimeTools.toJavaInstant(JODA_LOCAL_DATE_TIME, JODA_ZONE)); + } + + // ================================ + // #endregion - toJavaInstant + // ================================ + + // ================================ + // #region - toJodaDateTime + // ================================ + + @Test + void toJodaDateTime_ZonedDateTime() { + assertEquals(JODA_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toJodaDateTime(ZONED_DATE_TIME_WITH_SYS_ZONE)); + assertEquals(JODA_DATE_TIME, DateTimeTools.toJodaDateTime(ZONED_DATE_TIME)); + } + + @Test + void toJodaDateTime_LocalDateTimeAndZoneId() { + assertEquals(JODA_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toJodaDateTime(LOCAL_DATE_TIME, SYS_ZONE_ID)); + assertEquals(JODA_DATE_TIME, DateTimeTools.toJodaDateTime(LOCAL_DATE_TIME, ZONE_ID)); + } + + @Test + void toJodaDateTime_InstantAndZoneId() { + assertEquals(JODA_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toJodaDateTime(INSTANT_WITH_SYS_ZONE, SYS_ZONE_ID)); + assertEquals(JODA_DATE_TIME, DateTimeTools.toJodaDateTime(INSTANT, ZONE_ID)); + } + + // ================================ + // #endregion - toJodaDateTime + // ================================ + + // ================================ + // #region - toZonedDateTime + // ================================ + + @Test + void toZonedDateTime_JodaDateTime() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(JODA_DATE_TIME_WITH_SYS_ZONE)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(JODA_DATE_TIME)); + } + + @Test + void toZonedDateTime_JodaLocalDateTimeAndJodaDateTimeZone() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(JODA_LOCAL_DATE_TIME, JODA_SYS_ZONE)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(JODA_LOCAL_DATE_TIME, JODA_ZONE)); + } + + @Test + void toZonedDateTime_JodaInstantAndJodaDateTimeZone() { + assertEquals(ZONED_DATE_TIME_WITH_SYS_ZONE, DateTimeTools.toZonedDateTime(JODA_INSTANT_WITH_SYS_ZONE, JODA_SYS_ZONE)); + assertEquals(ZONED_DATE_TIME, DateTimeTools.toZonedDateTime(JODA_INSTANT, JODA_ZONE)); + } + + // ================================ + // #endregion - toZonedDateTime + // ================================ + + // ================================ + // #region - toJodaLocalDateTime + // ================================ + + @Test + void toJodaLocalDateTime_JavaLocalDateTime() { + assertEquals(JODA_LOCAL_DATE_TIME, DateTimeTools.toJodaLocalDateTime(LOCAL_DATE_TIME)); + } + + @Test + void toJavaLocalDateTime_JodaLocalDateTime() { + assertEquals(LOCAL_DATE_TIME, DateTimeTools.toJavaLocalDateTime(JODA_LOCAL_DATE_TIME)); + } + + // ================================ + // #endregion - toJodaLocalDateTime + // ================================ + + // ================================ + // #region - ZondId <--> DateTimeZone + // ================================ + + @Test + void convertJavaZoneIdAndJodaDateTimeZone() { + assertEquals(SYS_ZONE_ID, DateTimeTools.toJavaZone(JODA_SYS_ZONE)); + assertEquals(ZONE_ID, DateTimeTools.toJavaZone(JODA_ZONE)); + assertEquals(JODA_SYS_ZONE, DateTimeTools.toJodaZone(SYS_ZONE_ID)); + assertEquals(JODA_ZONE, DateTimeTools.toJodaZone(ZONE_ID)); + } + + // ================================ + // #endregion - ZondId <--> DateTimeZone + // ================================ + + // ================================ + // #region - YearQuarter & Quarter + // ================================ + + @Test + void getQuarter() { + YearQuarter expectedYearQuarter = YearQuarter.of(2024, 4); + assertEquals(expectedYearQuarter, DateTimeTools.getQuarter(SYS_DATE)); + assertEquals(expectedYearQuarter, DateTimeTools.getQuarter(SYS_CALENDAR)); + assertEquals(Quarter.Q4, DateTimeTools.getQuarter(Month.DECEMBER)); + assertEquals(expectedYearQuarter, DateTimeTools.getQuarter(2024, Month.DECEMBER)); + assertEquals(expectedYearQuarter, DateTimeTools.getQuarter(YearMonth.of(2024, Month.DECEMBER))); + assertEquals(expectedYearQuarter, DateTimeTools.getQuarter(LOCAL_DATE)); + } + + // ================================ + // #endregion - YearQuarter & Quarter + // ================================ + + // ================================ + // #region - others + // ================================ + + @Test + void startDateOfYear() { + assertEquals(LocalDate.of(2008, 1, 1), DateTimeTools.startDateOfYear(2008)); + assertEquals(LocalDate.of(2008, 12, 31), DateTimeTools.endDateOfYear(2008)); + assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0), + DateTimeTools.startOfNextDate(LOCAL_DATE)); + assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0).atZone(SYS_ZONE_ID), + DateTimeTools.startOfNextDate(LOCAL_DATE, SYS_ZONE_ID)); + assertEquals(LocalDateTime.of(2024, 12, 30, 0, 0, 0).atZone(ZONE_ID), + DateTimeTools.startOfNextDate(LOCAL_DATE, ZONE_ID)); + + Range localDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE); + assertEquals(LOCAL_DATE.atStartOfDay(), localDateTimeRange.lowerEndpoint()); + assertTrue(localDateTimeRange.contains(LOCAL_DATE.atStartOfDay())); + assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint()); + assertEquals(LocalDate.of(2024, 12, 30).atStartOfDay(), localDateTimeRange.upperEndpoint()); + assertFalse(localDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay())); + + Range zonedDateTimeRange = DateTimeTools.toDateTimeRange(LOCAL_DATE, SYS_ZONE_ID); + assertEquals(LOCAL_DATE.atStartOfDay().atZone(SYS_ZONE_ID), zonedDateTimeRange.lowerEndpoint()); + assertTrue(zonedDateTimeRange.contains(LOCAL_DATE.atStartOfDay().atZone(SYS_ZONE_ID))); + assertEquals(ZonedDateTime.of(LocalDate.of(2024, 12, 30).atStartOfDay(), SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint()); + assertEquals(ZonedDateTime.of(LocalDate.of(2024, 12, 30).atStartOfDay(), SYS_ZONE_ID), zonedDateTimeRange.upperEndpoint()); + assertFalse(zonedDateTimeRange.contains(LocalDate.of(2024, 12, 30).atStartOfDay().atZone(SYS_ZONE_ID))); + } + + // ================================ + // #endregion - others + // ================================ + + // ================================ + // #region - toString + // ================================ + + @Test + void testToString() { + assertEquals("2024", DateTimeTools.toYearString(2024)); + assertEquals("999999999", DateTimeTools.toYearString(Year.MAX_VALUE)); + assertEquals("-999999999", DateTimeTools.toYearString(Year.MIN_VALUE)); + assertThrows(DateTimeException.class, () -> DateTimeTools.toYearString(Year.MIN_VALUE - 1)); + assertThrows(DateTimeException.class, () -> DateTimeTools.toYearString(Year.MAX_VALUE + 1)); + + assertEquals("01", DateTimeTools.toMonthStringMM(1)); + assertEquals("02", DateTimeTools.toMonthStringMM(2)); + assertEquals("3", DateTimeTools.toMonthStringM(3)); + assertEquals("04", DateTimeTools.toMonthStringMM(Month.APRIL)); + assertEquals("05", DateTimeTools.toMonthStringMM(Month.MAY)); + assertEquals("6", DateTimeTools.toMonthStringM(Month.JUNE)); + + assertThrows(DateTimeException.class, () -> DateTimeTools.toMonthStringM(0)); + assertThrows(DateTimeException.class, () -> DateTimeTools.toMonthStringMM(0)); + assertThrows(DateTimeException.class, () -> DateTimeTools.toMonthStringM(13)); + assertThrows(DateTimeException.class, () -> DateTimeTools.toMonthStringMM(13)); + } + + // ================================ + // #endregion - toString + // ================================ } diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/EnumToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/EnumToolsTests.java new file mode 100644 index 0000000..d4442cd --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/EnumToolsTests.java @@ -0,0 +1,225 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +@SuppressWarnings("deprecation") +public +class EnumToolsTests { + + private enum MyEnum { + VALUE_0, + VALUE_1, + VALUE_2, + VALUE_3, + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 3 }) + void testCheckOrdinal_success(int ordinal) { + assertEquals(ordinal, EnumTools.checkOrdinal(MyEnum.class, ordinal)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 4 }) + void testCheckOrdinal_EnumConstantNotPresentException(int ordinal) { + assertThrows(EnumConstantNotPresentException.class, + () -> EnumTools.checkOrdinal(MyEnum.class, ordinal)); + } + + @Test + void testCheckOrdinal_null() { + assertThrows(NullPointerException.class, + () -> EnumTools.checkOrdinal(MyEnum.class, null)); + assertThrows(NullPointerException.class, + () -> EnumTools.checkOrdinal(null, 0)); + assertThrows(NullPointerException.class, + () -> EnumTools.checkOrdinal(null, null)); + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 3 }) + void testCheckOrdinalNullable_success(int ordinal) { + assertEquals(ordinal, EnumTools.checkOrdinalNullable(MyEnum.class, ordinal)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 4 }) + void testCheckOrdinalNullable_EnumConstantNotPresentException(int ordinal) { + assertThrows(EnumConstantNotPresentException.class, () -> { + EnumTools.checkOrdinalNullable(MyEnum.class, ordinal); + }); + } + + @Test + void testCheckOrdinalNullable_null() { + assertNull(EnumTools.checkOrdinalNullable(MyEnum.class, null)); + + assertThrows(NullPointerException.class, () -> { + EnumTools.checkOrdinalNullable(null, 0); + }); + + assertThrows(NullPointerException.class, () -> { + EnumTools.checkOrdinalNullable(null, null); + }); + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, 2, 3 }) + void testCheckOrdinalOrDefault_0To3(int ordinal) { + assertEquals(ordinal, EnumTools.checkOrdinalOrDefault(MyEnum.class, ordinal)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 4 }) + void testCheckOrdinalOrDefault_EnumConstantNotPresentException(int ordinal) { + assertThrows(EnumConstantNotPresentException.class, () -> { + EnumTools.checkOrdinalOrDefault(MyEnum.class, ordinal); + }); + assertThrows(EnumConstantNotPresentException.class, () -> { + EnumTools.checkOrdinalOrDefault(MyEnum.class, null, ordinal); + }); + } + + @Test + void testCheckOrdinalOrDefault_null() { + assertEquals(0, EnumTools.checkOrdinalOrDefault(MyEnum.class, null)); + assertEquals(1, EnumTools.checkOrdinalOrDefault(MyEnum.class, null, 1)); + assertNull(EnumTools.checkOrdinalOrDefault(MyEnum.class, null, null)); + } + + @Test + void testGetValueNullable_0To3() { + assertSame(MyEnum.VALUE_0, EnumTools.getValueNullable(MyEnum.class, 0)); + assertSame(MyEnum.VALUE_1, EnumTools.getValueNullable(MyEnum.class, 1)); + assertSame(MyEnum.VALUE_2, EnumTools.getValueNullable(MyEnum.class, 2)); + assertSame(MyEnum.VALUE_3, EnumTools.getValueNullable(MyEnum.class, 3)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 4 }) + void testGetValueNullable_EnumConstantNotPresentException(int ordinal) { + assertThrows(EnumConstantNotPresentException.class, () -> { + EnumTools.getValueNullable(MyEnum.class, ordinal); + }); + } + + @Test + void testGetValueNullable_null() { + assertNull(EnumTools.getValueNullable(MyEnum.class, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueNullable(null, 0)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueNullable(null, null)); + } + + @Test + void testGetValueOrDefault_0To3() { + assertSame(MyEnum.VALUE_0, EnumTools.getValueOrDefault(MyEnum.class, 0)); + assertSame(MyEnum.VALUE_1, EnumTools.getValueOrDefault(MyEnum.class, 1)); + assertSame(MyEnum.VALUE_2, EnumTools.getValueOrDefault(MyEnum.class, 2)); + assertSame(MyEnum.VALUE_3, EnumTools.getValueOrDefault(MyEnum.class, 3)); + + assertSame(MyEnum.VALUE_0, EnumTools.getValueOrDefault(MyEnum.class, 0, () -> MyEnum.VALUE_1)); + assertSame(MyEnum.VALUE_1, EnumTools.getValueOrDefault(MyEnum.class, 1, () -> MyEnum.VALUE_0)); + assertSame(MyEnum.VALUE_2, EnumTools.getValueOrDefault(MyEnum.class, 2, () -> MyEnum.VALUE_0)); + assertSame(MyEnum.VALUE_3, EnumTools.getValueOrDefault(MyEnum.class, 3, () -> MyEnum.VALUE_0)); + + assertSame(MyEnum.VALUE_0, EnumTools.getValueOrDefault(MyEnum.class, 0, () -> null)); + assertSame(MyEnum.VALUE_1, EnumTools.getValueOrDefault(MyEnum.class, 1, () -> null)); + assertSame(MyEnum.VALUE_2, EnumTools.getValueOrDefault(MyEnum.class, 2, () -> null)); + assertSame(MyEnum.VALUE_3, EnumTools.getValueOrDefault(MyEnum.class, 3, () -> null)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 4 }) + void testGetValueOrDefault_EnumConstantNotPresentException(int ordinal) { + assertThrows(EnumConstantNotPresentException.class, () -> { + EnumTools.getValueOrDefault(MyEnum.class, ordinal); + }); + assertThrows(EnumConstantNotPresentException.class, () -> { + EnumTools.getValueOrDefault(MyEnum.class, ordinal, () -> MyEnum.VALUE_0); + }); + } + + @Test + void testGetValueOrDefault_null() { + assertSame(MyEnum.VALUE_0, EnumTools.getValueOrDefault(MyEnum.class, null)); + assertSame(MyEnum.VALUE_0, EnumTools.getValueOrDefault(MyEnum.class, null, () -> MyEnum.VALUE_0)); + assertNull(EnumTools.getValueOrDefault(MyEnum.class, null, () -> null)); + + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, -1)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, 0)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, 4)); + + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, null, () -> MyEnum.VALUE_0)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, -1, () -> MyEnum.VALUE_1)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, 0, () -> MyEnum.VALUE_1)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, 4, () -> MyEnum.VALUE_0)); + + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(MyEnum.class, null, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(MyEnum.class, -1, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(MyEnum.class, 0, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(MyEnum.class, 4, null)); + + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, null, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, -1, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, 0, null)); + assertThrows(NullPointerException.class, () -> EnumTools.getValueOrDefault(null, 4, null)); + + } + + @Test + void testValueOf_0To3() { + assertSame(MyEnum.VALUE_0, EnumTools.valueOf(MyEnum.class, 0)); + assertSame(MyEnum.VALUE_1, EnumTools.valueOf(MyEnum.class, 1)); + assertSame(MyEnum.VALUE_2, EnumTools.valueOf(MyEnum.class, 2)); + assertSame(MyEnum.VALUE_3, EnumTools.valueOf(MyEnum.class, 3)); + + assertSame(MyEnum.VALUE_0, EnumTools.valueOf(MyEnum.class, 0, MyEnum.VALUE_1)); + assertSame(MyEnum.VALUE_1, EnumTools.valueOf(MyEnum.class, 1, MyEnum.VALUE_0)); + assertSame(MyEnum.VALUE_2, EnumTools.valueOf(MyEnum.class, 2, MyEnum.VALUE_0)); + assertSame(MyEnum.VALUE_3, EnumTools.valueOf(MyEnum.class, 3, MyEnum.VALUE_0)); + + assertSame(MyEnum.VALUE_0, EnumTools.valueOf(MyEnum.class, 0, null)); + assertSame(MyEnum.VALUE_1, EnumTools.valueOf(MyEnum.class, 1, null)); + assertSame(MyEnum.VALUE_2, EnumTools.valueOf(MyEnum.class, 2, null)); + assertSame(MyEnum.VALUE_3, EnumTools.valueOf(MyEnum.class, 3, null)); + } + + @ParameterizedTest + @ValueSource(ints = { -1, 4 }) + void testValueOf_EnumConstantNotPresentException(int ordinal) { + assertThrows(EnumConstantNotPresentException.class, () -> EnumTools.valueOf(MyEnum.class, ordinal)); + assertThrows(EnumConstantNotPresentException.class, () -> EnumTools.valueOf(MyEnum.class, ordinal, MyEnum.VALUE_0)); + assertThrows(EnumConstantNotPresentException.class, () -> EnumTools.valueOf(MyEnum.class, ordinal, null)); + } + + @Test + void testValueOf_null() { + assertThrows(NullPointerException.class, () -> EnumTools.valueOf(null, 0)); + assertSame(MyEnum.VALUE_0, EnumTools.valueOf(MyEnum.class, null, MyEnum.VALUE_0)); + assertNull(EnumTools.valueOf(MyEnum.class, null, null)); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/IdGeneratorTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/IdGeneratorTests.java new file mode 100644 index 0000000..ccc3f59 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/IdGeneratorTests.java @@ -0,0 +1,86 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import cn.hutool.core.collection.ConcurrentHashSet; + +public class IdGeneratorTests { + + final ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue()); + + @Test + void testSnowflakeIdGenerator() { // NOSONAR + final SnowflakeIdGenerator snowflake = new SnowflakeIdGenerator(0, 0); + final Set ids = new ConcurrentHashSet<>(); + for (int i = 0; i < 10000; i++) { + executor.execute(() -> { + for (int j = 0; j < 50000; j++) { + if (false == ids.add(snowflake.nextId())) { + throw new RuntimeException("重复ID!"); + } + } + }); + } + } + + @ParameterizedTest + @ValueSource(longs = { 0L, 1L, 108L, 300L }) + void testIdWorker(long workerId) { // NOSONAR + // 如果使用 new IdWorker(0L) 创建,会和下面的 IdGenerator#nextSnowflakeId 使用相同 workerId 的不同 IdWorker 实例,造成 ID 重复 + final IdWorker idWorker = IdGenerator.getSnowflakeIdGenerator(workerId); + + final Set ids = new ConcurrentHashSet<>(); + for (int i = 0; i < 10000; i++) { + executor.execute(() -> { + for (int j = 0; j < 50000; j++) { + if (false == ids.add(idWorker.nextId())) { + throw new RuntimeException("重复ID!"); + } + } + }); + executor.execute(() -> { + for (int j = 0; j < 50000; j++) { + if (false == ids.add(IdGenerator.nextSnowflakeId(workerId))) { + throw new RuntimeException("重复ID!"); + } + } + }); + } + } + + @Test + void testToSimpleString() { + UUID id = UUID.randomUUID(); + assertEquals(id.toString().replaceAll("-", ""), + IdGenerator.toSimpleString(id)); + } + +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/MyBatisSqlBuilderTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/MyBatisSqlBuilderTests.java deleted file mode 100644 index 419914a..0000000 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/MyBatisSqlBuilderTests.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2023-2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.util; - -import static xyz.zhouxy.plusone.commons.sql.MyBatisSql.IN; - -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import xyz.zhouxy.plusone.commons.sql.MyBatisSql; - -class MyBatisSqlBuilderTests { - private static final Logger log = LoggerFactory.getLogger(MyBatisSqlBuilderTests.class); - - @Test - void test() { - // List ids = Arrays.asList("2333", "4501477"); - MyBatisSql sql = MyBatisSql.newScriptSql() - .SELECT("*") - .FROM("test_table") - .WHERE(IN("id", "ids")); - log.info("sql: {}", sql); - } -} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/NumbersTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/NumbersTests.java new file mode 100644 index 0000000..1e2435f --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/NumbersTests.java @@ -0,0 +1,187 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import org.junit.jupiter.api.Test; +import java.math.BigDecimal; +import java.math.BigInteger; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public +class NumbersTests { + + @Test + public void sum_ShortArray_ReturnsCorrectSum() { + short[] numbers = {1, 2, 3, 4}; + int result = Numbers.sum(numbers); + assertEquals(10, result); + } + + @Test + public void sum_IntArray_ReturnsCorrectSum() { + int[] numbers = {1, 2, 3, 4}; + long result = Numbers.sum(numbers); + assertEquals(10L, result); + } + + @Test + public void sum_LongArray_ReturnsCorrectSum() { + long[] numbers = {1, 2, 3, 4}; + long result = Numbers.sum(numbers); + assertEquals(10L, result); + } + + @Test + public void sum_FloatArray_ReturnsCorrectSum() { + float[] numbers = {1.5f, 2.5f, 3.5f}; + double result = Numbers.sum(numbers); + assertEquals(7.5, result); + } + + @Test + public void sum_DoubleArray_ReturnsCorrectSum() { + double[] numbers = {1.5, 2.5, 3.5}; + double result = Numbers.sum(numbers); + assertEquals(7.5, result); + } + + @Test + public void sum_BigIntegerArray_ReturnsCorrectSum() { + BigInteger[] numbers = {new BigInteger("1"), new BigInteger("2"), new BigInteger("3")}; + BigInteger result = Numbers.sum(numbers); + assertEquals(new BigInteger("6"), result); + } + + @Test + public void sum_BigDecimalArray_ReturnsCorrectSum() { + BigDecimal[] numbers = {new BigDecimal("1.5"), new BigDecimal("2.5"), new BigDecimal("3.5")}; + BigDecimal result = Numbers.sum(numbers); + assertEquals(new BigDecimal("7.5"), result); + } + + @Test + public void nullToZero_ByteNotNull_ReturnsSameValue() { + Byte value = 5; + byte result = Numbers.nullToZero(value); + assertEquals(5, result); + } + + @Test + public void nullToZero_ByteNull_ReturnsZero() { + Byte value = null; + byte result = Numbers.nullToZero(value); + assertEquals(0, result); + } + + @Test + public void nullToZero_ShortNotNull_ReturnsSameValue() { + Short value = 5; + short result = Numbers.nullToZero(value); + assertEquals(5, result); + } + + @Test + public void nullToZero_ShortNull_ReturnsZero() { + Short value = null; + short result = Numbers.nullToZero(value); + assertEquals(0, result); + } + + @Test + public void nullToZero_IntegerNotNull_ReturnsSameValue() { + Integer value = 5; + int result = Numbers.nullToZero(value); + assertEquals(5, result); + } + + @Test + public void nullToZero_IntegerNull_ReturnsZero() { + Integer value = null; + int result = Numbers.nullToZero(value); + assertEquals(0, result); + } + + @Test + public void nullToZero_LongNotNull_ReturnsSameValue() { + Long value = 5L; + long result = Numbers.nullToZero(value); + assertEquals(5L, result); + } + + @Test + public void nullToZero_LongNull_ReturnsZero() { + Long value = null; + long result = Numbers.nullToZero(value); + assertEquals(0L, result); + } + + @Test + public void nullToZero_FloatNotNull_ReturnsSameValue() { + Float value = 5.0F; + float result = Numbers.nullToZero(value); + assertEquals(5.0F, result); + } + + @Test + public void nullToZero_FloatNull_ReturnsZero() { + Float value = null; + float result = Numbers.nullToZero(value); + assertEquals(0.0F, result); + } + + @Test + public void nullToZero_DoubleNotNull_ReturnsSameValue() { + Double value = 5.0; + double result = Numbers.nullToZero(value); + assertEquals(5.0, result); + } + + @Test + public void nullToZero_DoubleNull_ReturnsZero() { + Double value = null; + double result = Numbers.nullToZero(value); + assertEquals(0.0, result); + } + + @Test + public void nullToZero_BigIntegerNotNull_ReturnsSameValue() { + BigInteger value = new BigInteger("5"); + BigInteger result = Numbers.nullToZero(value); + assertEquals(new BigInteger("5"), result); + } + + @Test + public void nullToZero_BigIntegerNull_ReturnsZero() { + BigInteger value = null; + BigInteger result = Numbers.nullToZero(value); + assertEquals(BigInteger.ZERO, result); + } + + @Test + public void nullToZero_BigDecimalNotNull_ReturnsSameValue() { + BigDecimal value = new BigDecimal("5.0"); + BigDecimal result = Numbers.nullToZero(value); + assertEquals(new BigDecimal("5.0"), result); + } + + @Test + public void nullToZero_BigDecimalNull_ReturnsZero() { + BigDecimal value = null; + BigDecimal result = Numbers.nullToZero(value); + assertEquals(BigDecimal.ZERO, result); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/OptionalToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/OptionalToolsTests.java new file mode 100644 index 0000000..79199e4 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/OptionalToolsTests.java @@ -0,0 +1,187 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; + +import org.junit.jupiter.api.Test; + +/** + * {@link OptionalTools} 单元测试 + */ +public +class OptionalToolsTests { + + @Test + void optionalOf_NullInteger_ReturnsEmptyOptionalInt() { + OptionalInt result = OptionalTools.optionalOf((Integer) null); + assertFalse(result.isPresent()); + } + + @Test + void optionalOf_ValidInteger_ReturnsOptionalIntWithValue() { + OptionalInt result = OptionalTools.optionalOf(10); + assertTrue(result.isPresent()); + assertEquals(10, result.getAsInt()); + } + + @Test + void toOptionalInt_NullOptionalInteger_ReturnsEmptyOptionalInt() { + OptionalInt result = OptionalTools.toOptionalInt(Optional.ofNullable(null)); + assertFalse(result.isPresent()); + } + + @Test + void toOptionalInt_ValidOptionalInteger_ReturnsOptionalIntWithValue() { + OptionalInt result = OptionalTools.toOptionalInt(Optional.of(10)); + assertTrue(result.isPresent()); + assertEquals(10, result.getAsInt()); + } + + @Test + void optionalOf_NullLong_ReturnsEmptyOptionalLong() { + OptionalLong result = OptionalTools.optionalOf((Long) null); + assertFalse(result.isPresent()); + } + + @Test + void optionalOf_ValidLong_ReturnsOptionalLongWithValue() { + OptionalLong result = OptionalTools.optionalOf(10L); + assertTrue(result.isPresent()); + assertEquals(10L, result.getAsLong()); + } + + @Test + void toOptionalLong_NullOptionalLong_ReturnsEmptyOptionalLong() { + OptionalLong result = OptionalTools.toOptionalLong(Optional.ofNullable(null)); + assertFalse(result.isPresent()); + } + + @Test + void toOptionalLong_ValidOptionalLong_ReturnsOptionalLongWithValue() { + OptionalLong result = OptionalTools.toOptionalLong(Optional.of(10L)); + assertTrue(result.isPresent()); + assertEquals(10L, result.getAsLong()); + } + + @Test + void optionalOf_NullDouble_ReturnsEmptyOptionalDouble() { + OptionalDouble result = OptionalTools.optionalOf((Double) null); + assertFalse(result.isPresent()); + } + + @Test + void optionalOf_ValidDouble_ReturnsOptionalDoubleWithValue() { + OptionalDouble result = OptionalTools.optionalOf(10.0); + assertTrue(result.isPresent()); + assertEquals(10.0, result.getAsDouble(), 0.0001); + } + + @Test + void toOptionalDouble_NullOptionalDouble_ReturnsEmptyOptionalDouble() { + OptionalDouble result = OptionalTools.toOptionalDouble(Optional.ofNullable(null)); + assertFalse(result.isPresent()); + } + + @Test + void toOptionalDouble_ValidOptionalDouble_ReturnsOptionalDoubleWithValue() { + OptionalDouble result = OptionalTools.toOptionalDouble(Optional.of(10.0)); + assertTrue(result.isPresent()); + assertEquals(10.0, result.getAsDouble(), 0.0001); + } + + @Test + void orElseNull_NullOptional_ThrowsNullPointerException() { + assertThrows(NullPointerException.class, + () -> OptionalTools.orElseNull(null)); + } + + @Test + void orElseNull_EmptyOptional_ReturnsNull() { + Object result = OptionalTools.orElseNull(Optional.empty()); + assertNull(result); + } + + @Test + void orElseNull_PresentOptional_ReturnsValue() { + Object result = OptionalTools.orElseNull(Optional.of("test")); + assertEquals("test", result); + } + + @Test + void toInteger_NullOptionalInt_ThrowsNullPointerException() { + assertThrows(NullPointerException.class, + () -> OptionalTools.toInteger(null)); + } + + @Test + void toInteger_EmptyOptionalInt_ReturnsNull() { + Integer result = OptionalTools.toInteger(OptionalInt.empty()); + assertNull(result); + } + + @Test + void toInteger_PresentOptionalInt_ReturnsValue() { + Integer result = OptionalTools.toInteger(OptionalInt.of(10)); + assertEquals(10, result); + } + + @Test + void toLong_NullOptionalLong_ThrowsNullPointerException() { + assertThrows(NullPointerException.class, + () -> OptionalTools.toLong(null)); + } + + @Test + void toLong_EmptyOptionalLong_ReturnsNull() { + Long result = OptionalTools.toLong(OptionalLong.empty()); + assertNull(result); + } + + @Test + void toLong_PresentOptionalLong_ReturnsValue() { + Long result = OptionalTools.toLong(OptionalLong.of(10L)); + assertEquals(10L, result); + } + + @Test + void toDouble_NullOptionalDouble_ThrowsNullPointerException() { + assertThrows(NullPointerException.class, + () -> OptionalTools.toDouble(null)); + } + + @Test + void toDouble_EmptyOptionalDouble_ReturnsNull() { + Double result = OptionalTools.toDouble(OptionalDouble.empty()); + assertNull(result); + } + + @Test + void toDouble_PresentOptionalDouble_ReturnsValue() { + Double result = OptionalTools.toDouble(OptionalDouble.of(10.0)); + assertEquals(10.0, result, 0.0001); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java new file mode 100644 index 0000000..2f63bc6 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/RandomToolsTests.java @@ -0,0 +1,105 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.security.SecureRandom; +import java.util.Random; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("null") +public class RandomToolsTests { + + private static Random random; + private static SecureRandom secureRandom; + private static char[] sourceCharactersArray; + private static String sourceCharactersString; + + @BeforeAll + public static void setUp() { + random = new Random(); + secureRandom = new SecureRandom(); + sourceCharactersArray = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray(); + sourceCharactersString = "abcdefghijklmnopqrstuvwxyz"; + } + + @Test + public void randomStr_NullRandom_ThrowsException() { + assertThrows(IllegalArgumentException.class, + () -> RandomTools.randomStr(null, sourceCharactersArray, 5)); + assertThrows(IllegalArgumentException.class, + () -> RandomTools.randomStr(null, sourceCharactersString, 5)); + } + + @Test + public void randomStr_NullSourceCharacters_ThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, (char[]) null, 5)); + assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, (String) null, 5)); + } + + @Test + public void randomStr_NegativeLength_ThrowsException() { + assertThrows(IllegalArgumentException.class, () -> RandomTools.randomStr(random, sourceCharactersArray, -1)); + } + + @Test + public void randomStr_ZeroLength_ReturnsEmptyString() { + assertAll( + () -> assertEquals("", RandomTools.randomStr(random, sourceCharactersArray, 0)), + () -> assertEquals("", RandomTools.randomStr(random, sourceCharactersString, 0)), + () -> assertEquals("", RandomTools.randomStr(secureRandom, sourceCharactersArray, 0)), + () -> assertEquals("", RandomTools.randomStr(secureRandom, sourceCharactersString, 0))); + } + + @Test + public void randomStr_PositiveLength_ReturnsRandomString() { + assertAll( + () -> assertEquals(5, RandomTools.randomStr(random, sourceCharactersArray, 5).length()), + () -> assertEquals(5, RandomTools.randomStr(random, sourceCharactersString, 5).length()), + () -> assertEquals(5, RandomTools.randomStr(secureRandom, sourceCharactersArray, 5).length()), + () -> assertEquals(5, RandomTools.randomStr(secureRandom, sourceCharactersString, 5).length())); + } + + @Test + public void randomStr_ReturnsRandomString() { + String result = RandomTools.randomStr(sourceCharactersArray, 5); + assertEquals(5, result.length()); + } + + @Test + public void randomStr_StringSourceCharacters_ReturnsRandomString() { + String result = RandomTools.randomStr(sourceCharactersString, 5); + assertEquals(5, result.length()); + } + + @Test + public void secureRandomStr_ReturnsRandomString() { + String result = RandomTools.secureRandomStr(sourceCharactersArray, 5); + assertEquals(5, result.length()); + } + + @Test + public void secureRandomStr_StringSourceCharacters_ReturnsRandomString() { + String result = RandomTools.secureRandomStr(sourceCharactersString, 5); + assertEquals(5, result.length()); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/RefTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/RefTests.java deleted file mode 100644 index 34e9fa3..0000000 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/RefTests.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2024 the original author or authors. - * - * Licensed 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 - * - * https://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 xyz.zhouxy.plusone.commons.util; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.Test; - -import lombok.extern.slf4j.Slf4j; -import xyz.zhouxy.plusone.commons.base.BoolRef; -import xyz.zhouxy.plusone.commons.base.CharRef; -import xyz.zhouxy.plusone.commons.base.DoubleRef; -import xyz.zhouxy.plusone.commons.base.IntRef; -import xyz.zhouxy.plusone.commons.base.LongRef; -import xyz.zhouxy.plusone.commons.base.Ref; - -@Slf4j -class RefTests { - - @Test - void testRef() { - Ref strRef = new Ref<>("ZhouXY"); - apply(strRef); - assertEquals("Hello ZhouXY", strRef.getValue()); - log.info("strRef: {}", strRef); - } - - void apply(Ref strRef) { - strRef.transform(str -> "Hello " + str); - } - - @Test - void testBoolRef() { - BoolRef boolRef = new BoolRef(false); - apply(boolRef); - assertTrue(boolRef.getValue()); - log.info("boolRef: {}", boolRef); - } - - void apply(BoolRef boolRef) { - boolRef.setValue(true); - } - - @Test - void testCharRef() { - CharRef charRef = new CharRef('T'); - - apply(false, charRef); - assertEquals('0', charRef.getValue()); - log.info("charRef: {}", charRef); - - apply(true, charRef); - assertEquals('1', charRef.getValue()); - log.info("charRef: {}", charRef); - } - - void apply(boolean condition, CharRef charRef) { - charRef.apply(c -> condition ? '1' : '0'); - } - - @Test - void testDoubleRef() { - DoubleRef doubleRef = new DoubleRef(2.33); - apply(88.108, doubleRef); - assertEquals(2.33 * 88.108, doubleRef.getValue()); - log.info("doubleRef: {}", doubleRef); - } - - void apply(double num, DoubleRef doubleRef) { - doubleRef.apply(d -> d * num); - } - - @Test - void testIntRef() { - IntRef intRef = new IntRef(108); - apply(88, intRef); - assertEquals(108 - 88, intRef.getValue()); - log.info("intRef: {}", intRef); - } - - void apply(int num, IntRef intRef) { - intRef.apply(d -> d - num); - } - - @Test - void testLongRef() { - LongRef longRef = new LongRef(108L); - apply(88L, longRef); - assertEquals(108L + 88L, longRef.getValue()); - log.info("intRef: {}", longRef); - } - - void apply(long num, LongRef longRef) { - longRef.apply(d -> d + num); - } -} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java new file mode 100644 index 0000000..0d3fb8d --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/RegexToolsTests.java @@ -0,0 +1,165 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public +class RegexToolsTests { + + @Test + void getPattern_CachePatternTrue_ReturnsCachedPattern() { + String pattern = "abc"; + Pattern cachedPattern = RegexTools.getPattern(pattern, true); + Pattern patternFromCache = RegexTools.getPattern(pattern, true); + assertSame(cachedPattern, patternFromCache, "Pattern should be cached"); + } + + @Test + void getPattern_CachePatternFalse_ReturnsNewPattern() { + String pattern = "getPattern_CachePatternFalse_ReturnsNewPattern"; + Pattern pattern1 = RegexTools.getPattern(pattern, false); + Pattern pattern2 = RegexTools.getPattern(pattern, false); + assertNotSame(pattern1, pattern2, "Pattern should not be cached"); + } + + @Test + void getPattern_NullPattern_ThrowsException() { + assertThrows(NullPointerException.class, () -> { + RegexTools.getPattern(null, true); + }); + } + + @Test + void getPatterns_CachePatternTrue_ReturnsCachedPatterns() { + String[] patterns = {"abc", "def"}; + Pattern[] cachedPatterns = RegexTools.getPatterns(patterns, true); + Pattern[] patternsFromCache = RegexTools.getPatterns(patterns, true); + assertSame(cachedPatterns[0], patternsFromCache[0]); + assertSame(cachedPatterns[1], patternsFromCache[1]); + } + + @Test + void getPatterns_CachePatternFalse_ReturnsNewPatterns() { + String[] patterns = {"getPatterns_CachePatternFalse_ReturnsNewPatterns1", "getPatterns_CachePatternFalse_ReturnsNewPatterns2"}; + Pattern[] patterns1 = RegexTools.getPatterns(patterns, false); + Pattern[] patterns2 = RegexTools.getPatterns(patterns, false); + assertNotSame(patterns1[0], patterns2[0]); + assertNotSame(patterns1[1], patterns2[1]); + } + + @Test + void getPatterns_NullPatterns_ThrowsException() { + assertThrows(NullPointerException.class, () -> { + RegexTools.getPatterns(null, true); + }); + } + + @Test + void matches_InputMatchesPattern_ReturnsTrue() { + String pattern = "abc"; + Pattern compiledPattern = Pattern.compile(pattern); + assertTrue(RegexTools.matches("abc", compiledPattern), "Input should match pattern"); + } + + @Test + void matches_InputDoesNotMatchPattern_ReturnsFalse() { + String pattern = "abc"; + Pattern compiledPattern = Pattern.compile(pattern); + assertFalse(RegexTools.matches("abcd", compiledPattern), "Input should not match pattern"); + } + + @Test + void matches_NullInput_ReturnsFalse() { + String pattern = "abc"; + Pattern compiledPattern = Pattern.compile(pattern); + assertFalse(RegexTools.matches(null, compiledPattern), "Null input should return false"); + } + + @Test + void matchesOne_InputMatchesOnePattern_ReturnsTrue() { + String[] patterns = {"abc", "def"}; + Pattern[] compiledPatterns = new Pattern[patterns.length]; + for (int i = 0; i < patterns.length; i++) { + compiledPatterns[i] = Pattern.compile(patterns[i]); + } + assertTrue(RegexTools.matchesOne("abc", compiledPatterns), "Input should match one pattern"); + } + + @Test + void matchesOne_InputDoesNotMatchAnyPattern_ReturnsFalse() { + String[] patterns = {"abc", "def"}; + Pattern[] compiledPatterns = new Pattern[patterns.length]; + for (int i = 0; i < patterns.length; i++) { + compiledPatterns[i] = Pattern.compile(patterns[i]); + } + assertFalse(RegexTools.matchesOne("xyz", compiledPatterns), "Input should not match any pattern"); + } + + @Test + void matchesAll_InputMatchesAllPatterns_ReturnsTrue() { + String[] patterns = {"abc", "abc"}; + Pattern[] compiledPatterns = new Pattern[patterns.length]; + for (int i = 0; i < patterns.length; i++) { + compiledPatterns[i] = Pattern.compile(patterns[i]); + } + assertTrue(RegexTools.matchesAll("abc", compiledPatterns), "Input should match all patterns"); + } + + @Test + void matchesAll_InputDoesNotMatchAllPatterns_ReturnsFalse() { + String[] patterns = {"abc", "def"}; + Pattern[] compiledPatterns = new Pattern[patterns.length]; + for (int i = 0; i < patterns.length; i++) { + compiledPatterns[i] = Pattern.compile(patterns[i]); + } + assertFalse(RegexTools.matchesAll("abc", compiledPatterns), "Input should not match all patterns"); + } + + @Test + void getMatcher_ValidInputAndPattern_ReturnsMatcher() { + String pattern = "abc"; + Pattern compiledPattern = Pattern.compile(pattern); + Matcher matcher = RegexTools.getMatcher("abc", compiledPattern); + assertNotNull(matcher, "Matcher should not be null"); + } + + @Test + void getMatcher_NullInput_ThrowsException() { + String pattern = "abc"; + Pattern compiledPattern = Pattern.compile(pattern); + assertThrows(NullPointerException.class, () -> { + RegexTools.getMatcher(null, compiledPattern); + }); + } + + @Test + void getMatcher_NullPattern_ThrowsException() { + final Pattern pattern = null; + assertThrows(NullPointerException.class, () -> { + RegexTools.getMatcher("abc", pattern); + }); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/StringToolsTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/StringToolsTests.java new file mode 100644 index 0000000..b383ee6 --- /dev/null +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/StringToolsTests.java @@ -0,0 +1,87 @@ +/* + * Copyright 2024 the original author or authors. + * + * Licensed 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 + * + * https://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 xyz.zhouxy.plusone.commons.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +public +class StringToolsTests { + + @Test + void isNotBlank_NullString_ReturnsFalse() { + assertFalse(StringTools.isNotBlank(null)); + } + + @Test + void isNotBlank_EmptyString_ReturnsFalse() { + assertFalse(StringTools.isNotBlank("")); + } + + @Test + void isNotBlank_WhitespaceString_ReturnsFalse() { + assertFalse(StringTools.isNotBlank(" ")); + } + + @Test + void isNotBlank_NonWhitespaceString_ReturnsTrue() { + assertTrue(StringTools.isNotBlank("Hello")); + } + + @Test + void repeat_NullString_ThrowsException() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + StringTools.repeat(null, 2); + }); + assertNull(exception.getMessage()); + } + + @Test + void repeat_EmptyString_ReturnsEmptyString() { + assertEquals("", StringTools.repeat("", 2)); + } + + @Test + void repeat_RepeatOnce_ReturnsOriginalString() { + assertEquals("Hello", StringTools.repeat("Hello", 1)); + } + + @Test + void repeat_RepeatMultipleTimes_ReturnsRepeatedString() { + assertEquals("HelloHelloHello", StringTools.repeat("Hello", 3)); + } + + @Test + void repeat_ExceedsMaxLength_ReturnsTruncatedString() { + assertEquals("HelloHello", StringTools.repeat("Hello", 2, 14)); + assertEquals("HelloHel", StringTools.repeat("Hello", 2, 8)); + assertEquals("He", StringTools.repeat("Hello", 2, 2)); + assertEquals("", StringTools.repeat("Hello", 0, 2)); + assertThrows(IllegalArgumentException.class, () -> StringTools.repeat("Hello", -1, 2)); + assertThrows(IllegalArgumentException.class, () -> StringTools.repeat("Hello", 5, -1)); + } + + @Test + void repeat_ZeroTimes_ReturnsEmptyString() { + assertEquals("", StringTools.repeat("Hello", 0)); + } +} diff --git a/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java b/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java index 753e267..4249c40 100644 --- a/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java +++ b/src/test/java/xyz/zhouxy/plusone/commons/util/TreeBuilderTests.java @@ -16,8 +16,14 @@ package xyz.zhouxy.plusone.commons.util; +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.Serializable; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -25,143 +31,203 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.gson.Gson; import cn.hutool.core.util.ObjectUtil; +import lombok.EqualsAndHashCode; import lombok.ToString; class TreeBuilderTests { private static final Logger log = LoggerFactory.getLogger(TreeBuilderTests.class); + + private final MenuItem A = MenuItem.of("A", "首页", "/home", 1); + private final MenuList B = MenuList.of("B", "系统管理", 3); + private final MenuItem B001 = /**/MenuItem.of("B", "B001", "功能管理", "/sys/function-mgmt", 4); + private final MenuItem B002 = /**/MenuItem.of("B", "B002", "角色管理", "/sys/role-mgmt", 3); + private final MenuItem B003 = /**/MenuItem.of("B", "B003", "账号管理", "/sys/account-mgmt", 2); + private final MenuItem B004 = /**/MenuItem.of("B", "B004", "系统参数管理", "/sys/param-mgmt", 1); + private final MenuList C = MenuList.of("C", "一级菜单C", 2); + private final MenuList C1 = /**/MenuList.of("C", "C1", "二级菜单C1", 3); + private final MenuItem C1001 = /**//**/MenuItem.of("C1", "C1001", "三级菜单C1001", "/c/c1/c1001", 1); + private final MenuItem C1002 = /**//**/MenuItem.of("C1", "C1002", "三级菜单C1002", "/c/c1/c1002", 2); + private final MenuItem C2 = /**/MenuItem.of("C", "C2", "二级菜单C2", "/c/c2", 1); + private final MenuItem C3 = /**/MenuItem.of("C", "C3", "二级菜单C3", "/c/c3", 2); + + private final List menus = ImmutableList.of(B, C1002, A, B004, C3, B001, B003, C1, C1001, B002, C, C2); + private final TreeBuilder treeBuilder = new TreeBuilder<>( Menu::getMenuCode, menu -> Optional.ofNullable(menu.parentMenuCode), MenuList::addChild, - (a, b) -> Integer.compare(a.getOrderNum(), b.getOrderNum())); + Menu.orderNumComparator); @Test - void testBuildTree() { - List menus = Lists.newArrayList( - MenuList.of("B", "系统管理", 3), - MenuItem.of("A", "首页", "/home", 1), - /**/MenuItem.of("B", "B002", "角色管理", "/sys/role-mgmt", 3), - /**/MenuItem.of("B", "B001", "功能管理", "/sys/function-mgmt", 4), - /**/MenuItem.of("B", "B004", "系统参数管理", "/sys/param-mgmt", 1), - /**/MenuItem.of("B", "B003", "账号管理", "/sys/account-mgmt", 2), - MenuList.of("C", "一级菜单C", 2), - /**/MenuItem.of("C", "C3", "二级菜单C3", "/c/c3", 2), - /**/MenuList.of("C", "C1", "二级菜单C1", 2), - /**//**/MenuItem.of("C1", "C1001", "三级菜单C1001", "/c/c1/c1001", 1), - /**//**/MenuItem.of("C1", "C1002", "三级菜单C1002", "/c/c1/c1002", 2), - /**/MenuItem.of("C", "C2", "二级菜单C2", "/c/c2", 1)); - - List clonedMenus; - - clonedMenus = menus.stream().map(m -> ObjectUtil.clone(m)).collect(Collectors.toList()); + void testBuildTreeAndSortedByOrderNum() { + List clonedMenus = menus.stream().map(ObjectUtil::clone).collect(Collectors.toList()); List menuTreeSortedByOrderNum = treeBuilder.buildTree(clonedMenus); log.info("menuTreeSortedByOrderNum: {}", new Gson().toJson(menuTreeSortedByOrderNum)); - clonedMenus = menus.stream().map(m -> ObjectUtil.clone(m)).collect(Collectors.toList()); + assertEquals(clonedMenus.stream() + .filter(menu -> menu.getParentMenuCode() == null) + .sorted(Menu.orderNumComparator) + .collect(Collectors.toList()), + menuTreeSortedByOrderNum); + + Map menuMap = new HashMap<>(); + for (Menu element : clonedMenus) { + menuMap.put(element.getMenuCode(), element); + } + + assertEquals(Arrays.stream(new Menu[] { B001, B002, B003, B004 }) + .sorted(Menu.orderNumComparator) + .collect(Collectors.toList()), + MenuList.class.cast(menuMap.get("B")).children); + + assertEquals(Arrays.stream(new Menu[] { C1, C2, C3 }) + .sorted(Menu.orderNumComparator) + .collect(Collectors.toList()), + MenuList.class.cast(menuMap.get("C")).children); + + assertEquals(Arrays.stream(new Menu[] { C1001, C1002 }) + .sorted(Menu.orderNumComparator) + .collect(Collectors.toList()), + MenuList.class.cast(menuMap.get("C1")).children); + + } + + @Test + void testBuildTreeAndSortedByMenuCode() { + List clonedMenus; + + clonedMenus = menus.stream().map(ObjectUtil::clone).collect(Collectors.toList()); List menuTreeSortedByMenuCode = treeBuilder.buildTree( clonedMenus, - (a, b) -> a.getMenuCode().compareTo(b.getMenuCode()) - ); + (a, b) -> a.getMenuCode().compareTo(b.getMenuCode())); log.info("menuTreeSortedByMenuCode: {}", new Gson().toJson(menuTreeSortedByMenuCode)); - } -} -@ToString -abstract class Menu implements Serializable { - protected final String parentMenuCode; - protected final String menuCode; - protected final String title; - protected final int orderNum; + assertEquals(clonedMenus.stream() + .filter(menu -> menu.getParentMenuCode() == null) + .sorted((a, b) -> a.getMenuCode().compareTo(b.getMenuCode())) + .collect(Collectors.toList()), + menuTreeSortedByMenuCode); - public Menu(String parentMenuCode, String menuCode, String title, int orderNum) { - this.parentMenuCode = parentMenuCode; - this.menuCode = menuCode; - this.title = title; - this.orderNum = orderNum; - } - - public String getMenuCode() { - return menuCode; - } - - public String getParentMenuCode() { - return parentMenuCode; - } - - public String getTitle() { - return title; - } - - public int getOrderNum() { - return orderNum; - } - - private static final long serialVersionUID = 20240917181424L; -} - -@ToString(callSuper = true) -class MenuItem extends Menu { - - private final String url; - - private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) { - super(parentMenuCode, menuCode, title, orderNum); - this.url = url; - } - - static MenuItem of(String parentMenuCode, String menuCode, String title, String url, int orderNum) { - return new MenuItem(parentMenuCode, menuCode, title, url, orderNum); - } - - static MenuItem of(String menuCode, String title, String url, int orderNum) { - return new MenuItem(null, menuCode, title, url, orderNum); - } - - public String getUrl() { - return url; - } - - private static final long serialVersionUID = 20240917181910L; -} - -@ToString(callSuper = true) -class MenuList extends Menu { - - private List children; - - private MenuList(String parentMenuCode, String menuCode, String title, int orderNum) { - super(parentMenuCode, menuCode, title, orderNum); - } - - static MenuList of(String parentMenuCode, String menuCode, String title, int orderNum) { - return new MenuList(parentMenuCode, menuCode, title, orderNum); - } - - static MenuList of(String menuCode, String title, int orderNum) { - return new MenuList(null, menuCode, title, orderNum); - } - - static MenuList of(String menuCode, String title, Iterable children, int orderNum) { - return of(null, menuCode, title, children, orderNum); - } - - static MenuList of(String parentMenuCode, String menuCode, String title, Iterable children, int orderNum) { - final MenuList instance = of(parentMenuCode, menuCode, title, orderNum); - children.forEach(instance::addChild); - return instance; - } - - public void addChild(Menu child) { - if (this.children == null) { - this.children = Lists.newArrayList(); + Map menuMap = new HashMap<>(); + for (Menu element : clonedMenus) { + menuMap.put(element.getMenuCode(), element); } - this.children.add(child); + + assertEquals(ImmutableList.of(B001, B002, B003, B004), + MenuList.class.cast(menuMap.get("B")).children); + + assertEquals(ImmutableList.of(C1, C2, C3), + MenuList.class.cast(menuMap.get("C")).children); + + assertEquals(ImmutableList.of(C1001, C1002), + MenuList.class.cast(menuMap.get("C1")).children); } - private static final long serialVersionUID = 20240917181917L; + @ToString + @EqualsAndHashCode + private static abstract class Menu implements Serializable { // NOSONAR + protected final String parentMenuCode; + protected final String menuCode; + protected final String title; + protected final int orderNum; + + public Menu(String parentMenuCode, String menuCode, String title, int orderNum) { + this.parentMenuCode = parentMenuCode; + this.menuCode = menuCode; + this.title = title; + this.orderNum = orderNum; + } + + public String getMenuCode() { + return menuCode; + } + + public String getParentMenuCode() { + return parentMenuCode; + } + + public String getTitle() { + return title; + } + + public int getOrderNum() { + return orderNum; + } + + public static Comparator orderNumComparator = + (a, b) -> Integer.compare(a.getOrderNum(), b.getOrderNum()); + + private static final long serialVersionUID = 20240917181424L; + } + + @ToString(callSuper = true) + @EqualsAndHashCode(callSuper = true) + private static final class MenuItem extends Menu { + + private final String url; + + private MenuItem(String parentMenuCode, String menuCode, String title, String url, int orderNum) { + super(parentMenuCode, menuCode, title, orderNum); + this.url = url; + } + + static MenuItem of(String parentMenuCode, String menuCode, String title, String url, int orderNum) { + return new MenuItem(parentMenuCode, menuCode, title, url, orderNum); + } + + static MenuItem of(String menuCode, String title, String url, int orderNum) { + return new MenuItem(null, menuCode, title, url, orderNum); + } + + public String getUrl() { + return url; + } + + private static final long serialVersionUID = 20240917181910L; + } + + @ToString(callSuper = true) + private static final class MenuList extends Menu { + + private List children; + + private MenuList(String parentMenuCode, String menuCode, String title, int orderNum) { + super(parentMenuCode, menuCode, title, orderNum); + } + + static MenuList of(String parentMenuCode, String menuCode, String title, int orderNum) { + return new MenuList(parentMenuCode, menuCode, title, orderNum); + } + + static MenuList of(String menuCode, String title, int orderNum) { + return new MenuList(null, menuCode, title, orderNum); + } + + @SuppressWarnings("unused") + static MenuList of(String menuCode, String title, Iterable children, int orderNum) { + return of(null, menuCode, title, children, orderNum); + } + + static MenuList of(String parentMenuCode, String menuCode, String title, Iterable children, + int orderNum) { + final MenuList instance = of(parentMenuCode, menuCode, title, orderNum); + children.forEach(instance::addChild); + return instance; + } + + public void addChild(Menu child) { + if (this.children == null) { + this.children = Lists.newArrayList(); + } + this.children.add(child); + } + + private static final long serialVersionUID = 20240917181917L; + } } diff --git a/src/test/resources/mybatis-config.xml b/src/test/resources/mybatis-config.xml new file mode 100644 index 0000000..903a7bb --- /dev/null +++ b/src/test/resources/mybatis-config.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/test/resources/xyz/zhouxy/plusone/commons/model/dto/test/AccountQueries/AccountQueries.xml b/src/test/resources/xyz/zhouxy/plusone/commons/model/dto/test/AccountQueries/AccountQueries.xml new file mode 100644 index 0000000..af25f08 --- /dev/null +++ b/src/test/resources/xyz/zhouxy/plusone/commons/model/dto/test/AccountQueries/AccountQueries.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + +