diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java index b834ecc..00619a3 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/AesUtil.java @@ -17,21 +17,12 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.crypto.BadPaddingException; import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; +import java.security.GeneralSecurityException; import java.util.Base64; -import java.util.Objects; import java.util.UUID; /** @@ -42,28 +33,26 @@ * The utility methods in this class are useful for scenarios where data needs to be securely * encrypted and decrypted. *

- * + * *

Example usage:

- *
- * {@code
+ * 
{@code
  * // Encrypting and decrypting byte array data
  * byte[] secretKey = "43f72073956d4c81".getBytes(StandardCharsets.UTF_8);
  * byte[] data = "Hello World".getBytes(StandardCharsets.UTF_8);
  * byte[] encryptedData = AesUtil.encrypt(data, secretKey);
  * byte[] decryptedData = AesUtil.decrypt(encryptedData, secretKey);
  * System.out.println(new String(decryptedData, StandardCharsets.UTF_8)); // Output: Hello World
- * 
+ *
  * // Encrypting and decrypting string data
  * String secret = "43f72073956d4c81";
  * String encryptedString = AesUtil.encrypt("Hello World", secret);
  * String decryptedString = AesUtil.decrypt(encryptedString, secret);
  * System.out.println(decryptedString); // Output: Hello World
- * 
+ *
  * // Generating a random secret key
  * String randomSecret = AesUtil.generateRandomSecret();
- * System.out.println(randomSecret); // Output: A ramdomly generated 16-character long secret 
- * }
- * 
+ * System.out.println(randomSecret); // Output: A randomly generated 16-character long secret + * }
* * @author hubin@baomidou * @version 1.1.0 @@ -71,81 +60,61 @@ */ public final class AesUtil { - private final static Logger log = LoggerFactory.getLogger(AesUtil.class); - /** - * Encrypts the data using the AES algorithm with the given secret. + * Encrypts the specified data using the AES algorithm with the provided secret key. * * @param data the data to be encrypted - * @param secret the secret to encrypt the data - * @return the encryption result or {@code null} if encryption failed + * @param secret the secret key used for encryption + * @return the encrypted data as a byte array + * @throws GeneralSecurityException if any cryptographic error occurs during encryption */ - public static byte[] encrypt(byte[] data, byte[] secret) { - try { - var secretKeySpec = new SecretKeySpec(new SecretKeySpec(secret, AES).getEncoded(), AES); - var cipher = Cipher.getInstance(AES_CBC_CIPHER); - cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); // set IV to secret - return cipher.doFinal(data); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | UnsupportedOperationException | - InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | - BadPaddingException exception) { - log.error(exception.getMessage()); - for (var stackTraceElement : exception.getStackTrace()) { - log.error("{}", stackTraceElement.toString()); - } - } - return null; + public static byte[] encrypt(byte[] data, byte[] secret) throws GeneralSecurityException { + var secretKeySpec = new SecretKeySpec(new SecretKeySpec(secret, AES).getEncoded(), AES); + var cipher = Cipher.getInstance(AES_CBC_CIPHER); + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); + return cipher.doFinal(data); } /** - * Decrypts the data using the AES algorithm with the given secret. + * Decrypts the specified data using the AES algorithm with the provided secret key. * - * @param data the data to be decrypted - * @param secret the secret to encrypt the data - * @return the decryption result or {@code null} if decryption failed + * @param data the data to be decrypted + * @param secret the secret key used for decryption + * @return the decrypted data as a byte array + * @throws GeneralSecurityException if any cryptographic error occurs during decryption */ - public static byte[] decrypt(byte[] data, byte[] secret) { - try { - var secretKeySpec = new SecretKeySpec(new SecretKeySpec(secret, AES).getEncoded(), AES); - var cipher = Cipher.getInstance(AES_CBC_CIPHER); - cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); // set IV to secret - return cipher.doFinal(data); - } catch (NoSuchAlgorithmException | NoSuchPaddingException | - UnsupportedOperationException | InvalidKeyException | - InvalidAlgorithmParameterException | IllegalBlockSizeException | - BadPaddingException exception) { - log.error(exception.getMessage()); - for (var stackTraceElement : exception.getStackTrace()) { - log.error("{}", stackTraceElement.toString()); - } - } - return null; + public static byte[] decrypt(byte[] data, byte[] secret) throws GeneralSecurityException { + var secretKeySpec = new SecretKeySpec(new SecretKeySpec(secret, AES).getEncoded(), AES); + var cipher = Cipher.getInstance(AES_CBC_CIPHER); + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(secret)); + return cipher.doFinal(data); } /** - * Encrypts the data using the AES algorithm with the given secret. + * Encrypts the specified string data using the AES algorithm with the provided secret key. * - * @param data the data to be encrypted - * @param secret the secret to encrypt the data - * @return the encryption result or {@code null} if encryption failed + * @param data the string data to be encrypted + * @param secret the secret key used for encryption + * @return the encrypted data encoded in Base64 + * @throws GeneralSecurityException if any cryptographic error occurs during encryption */ - public static String encrypt(String data, String secret) { + public static String encrypt(String data, String secret) throws GeneralSecurityException { return Base64.getEncoder().encodeToString(encrypt(data.getBytes(StandardCharsets.UTF_8), secret.getBytes(StandardCharsets.UTF_8))); } /** - * Decrypts the data using the AES algorithm with the given secret. + * Decrypts the specified Base64-encoded string data using the AES algorithm with the provided secret key. * - * @param data the data to be decrypted - * @param secret the secret to encrypt the data - * @return the decryption result or {@code null} if decryption failed + * @param data the Base64-encoded string data to be decrypted + * @param secret the secret key used for decryption + * @return the decrypted string data + * @throws GeneralSecurityException if any cryptographic error occurs during decryption */ - public static String decrypt(String data, String secret) { - return new String(Objects.requireNonNull( - decrypt(Base64.getDecoder().decode(data.getBytes()), - secret.getBytes(StandardCharsets.UTF_8))) - ); + public static String decrypt(String data, String secret) throws GeneralSecurityException { + var decrypted = decrypt(Base64.getDecoder().decode(data.getBytes(StandardCharsets.UTF_8)), + secret.getBytes(StandardCharsets.UTF_8)); + return new String(decrypted, StandardCharsets.UTF_8); } /** diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java index 4062a0a..93d0347 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/Base64Util.java @@ -17,9 +17,6 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Base64; @@ -58,8 +55,6 @@ */ public final class Base64Util { - private final static Logger log = LoggerFactory.getLogger(Base64Util.class); - /** * Ensure that there is only one Base64 Encoder. * diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BoolUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BoolUtil.java index 310eb7a..4bf81da 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BoolUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BoolUtil.java @@ -17,9 +17,6 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Arrays; import java.util.Objects; import java.util.function.BooleanSupplier; @@ -47,8 +44,6 @@ */ public final class BoolUtil { - private final static Logger log = LoggerFactory.getLogger(BoolUtil.class); - /** * Logical and calculation. * diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java index 6f9f136..f493a70 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/BranchUtil.java @@ -206,18 +206,4 @@ public void then(Runnable trueHandler) { then(trueHandler, null); } - /** - * Get the boolean result. - *

- * Note: {@link BranchUtil} is not responsible for getting a raw boolean result, consider use - * {@link BoolUtil} to replace. - * - * @return the result - * @see BoolUtil - */ - @Deprecated(forRemoval = true) - public boolean getResult() { - return result; - } - } diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/CollectionUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/CollectionUtil.java index 44c75c0..fd4534a 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/CollectionUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/CollectionUtil.java @@ -17,9 +17,6 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -33,8 +30,6 @@ */ public final class CollectionUtil { - private static final Logger log = LoggerFactory.getLogger(CollectionUtil.class); - /** * Private constructor to prevent instantiation of this utility class. */ @@ -68,7 +63,7 @@ public static > List chunk(C originalCollection, throw new IllegalArgumentException("Collection must not be null."); } - if (maxSize < 0) { + if (maxSize <= 0) { throw new IllegalArgumentException("maxSize must greater than 0."); } diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java index ca98667..8fa7156 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/HashUtil.java @@ -17,14 +17,10 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.Objects; import java.util.Optional; /** @@ -70,8 +66,6 @@ */ public final class HashUtil { - private final static Logger log = LoggerFactory.getLogger(HashUtil.class); - /** * Calculates the MD2 hash value of the specified string using the given charset. * diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ListUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ListUtil.java deleted file mode 100644 index f1ed1ba..0000000 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/ListUtil.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2024-2025 OnixByte. - * - * 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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.devkit.utils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * A utility class providing static methods for manipulating lists. - * - * @author siujamo - * @author zihluwang - */ -public final class ListUtil { - - /** - * Private constructor to prevent instantiation of this utility class. - */ - private ListUtil() { - } - - /** - * Splits a given List into a List of sub lists, where each sublist contains at most - * {@code maxSize} elements. The original list is not modified, and new sub lists are created - * to hold the partitioned data. - *

- * If the original list's size is less than or equal to {@code maxSize}, a single sublist - * containing all elements is returned. If the list is empty, an empty list of sub lists - * is returned. - * - * @param the type of elements in the list - * @param originalList the list to be split, must not be null - * @param maxSize the maximum number of elements in each sublist, must be positive - * @param listFactory list factory - * @return a List of sub lists, where each sublist has at most {@code maxSize} elements - * @throws IllegalArgumentException if {@code originalList} is null or {@code maxSize} is less - * than or equal to 0 - */ - public static List> chunk(List originalList, int maxSize, Supplier> listFactory) { - // check input - if (Objects.isNull(originalList)) { - throw new IllegalArgumentException("List cannot be null"); - } - - if (maxSize <= 0) { - throw new IllegalArgumentException("Max size should be greater than 0"); - } - - if (Objects.isNull(listFactory)) { - throw new IllegalArgumentException("List factory cannot be null"); - } - - var result = new ArrayList>(); - var size = originalList.size(); - - // if the original list is empty or smaller than maxSize, return it as a single sublist - if (size <= maxSize) { - var singleSubList = listFactory.get(); - singleSubList.addAll(originalList); - result.add(singleSubList); - return result; - } - - // split the list - for (var i = 0; i < size; i += maxSize) { - var end = Math.min(i + maxSize, size); // ensure not to exceed list length - var subList = originalList.subList(i, end); - var subListWrapper = listFactory.get(); - subListWrapper.addAll(subList); - result.add(subListWrapper); // create a new list to avoid reference issues - } - - return result; - } - - /** - * Splits a given List into a List of sub lists, where each sublist contains at most - * {@code maxSize} elements. The original list is not modified, and new sub lists are created - * to hold the partitioned data. - *

- * If the original list's size is less than or equal to {@code maxSize}, a single sublist - * containing all elements is returned. If the list is empty, an empty list of sub lists - * is returned. - * - * @param the type of elements in the list - * @param originalList the list to be split, must not be null - * @param maxSize the maximum number of elements in each sublist, must be positive - * @return a List of sub lists, where each sublist has at most {@code maxSize} elements - * @throws IllegalArgumentException if {@code originalList} is null or {@code maxSize} is less - * than or equal to 0 - * @see #chunk(List, int, Supplier) - */ - public static List> chunk(List originalList, int maxSize) { - return chunk(originalList, maxSize, ArrayList::new); - } - -} diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java index 7f489f5..4fa5d78 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/MapUtil.java @@ -17,9 +17,6 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.Map; /** @@ -85,8 +82,6 @@ */ public final class MapUtil { - private final static Logger log = LoggerFactory.getLogger(MapUtil.class); - /** * Converts an object to a map by mapping the field names to their corresponding values. * diff --git a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/RangeUtil.java b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/RangeUtil.java index 34681fa..2437e8b 100644 --- a/devkit-utils/src/main/java/com/onixbyte/devkit/utils/RangeUtil.java +++ b/devkit-utils/src/main/java/com/onixbyte/devkit/utils/RangeUtil.java @@ -17,9 +17,6 @@ package com.onixbyte.devkit.utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.stream.IntStream; /** @@ -34,8 +31,6 @@ */ public final class RangeUtil { - private final static Logger log = LoggerFactory.getLogger(RangeUtil.class); - /** * Private constructor to prevent instantiation of this utility class. */ @@ -68,7 +63,7 @@ private RangeUtil() { */ public static IntStream range(int end) { if (end <= 0) { - throw new IllegalArgumentException("Parameter [end] should not less than 0, provided is " + + throw new IllegalArgumentException("Parameter [end] should not be less than or equal to 0, provided: " + end); } return IntStream.range(0, end); @@ -81,6 +76,10 @@ public static IntStream range(int end) { * It creates a sequential, ordered {@code IntStream} that can be used for iteration or * further processing. *

+ * If {@code start} is less than {@code end}, an ascending range (exclusive of {@code end}) + * is generated. If {@code start} is greater than {@code end}, a descending range (exclusive of {@code end}) + * is generated. If {@code start} equals {@code end}, an empty stream is returned. + *

* Example Usage: *

{@code
      * RangeUtil.range(3, 8).forEach(System.out::println);
@@ -91,20 +90,32 @@ public static IntStream range(int end) {
      * // 5
      * // 6
      * // 7
+     *
+     * RangeUtil.range(8, 3).forEach(System.out::println);
+     *
+     * // Output:
+     * // 8
+     * // 7
+     * // 6
+     * // 5
+     * // 4
      * }
* * @param start the starting value of the range (inclusive) * @param end upper-bound of the range (exclusive) - * @return an {@code IntStream} of integers from {@code 0} (inclusive) to - * {@code end} (exclusive) - * @throws IllegalArgumentException if the given {@code end} value is less equal to 0 + * @return an {@code IntStream} of integers in ascending or descending order, exclusive of {@code end} * @see IntStream */ public static IntStream range(int start, int end) { - if (end >= start) { - throw new IllegalStateException("Parameter [start] should less than parameter [end]."); + if (start == end) { + return IntStream.empty(); + } + if (start < end) { + return IntStream.range(start, end); + } else { + // Descending range (exclusive of end) + return IntStream.iterate(start, n -> n > end, n -> n - 1); } - return IntStream.range(start, end); } /** @@ -114,6 +125,8 @@ public static IntStream range(int start, int end) { * It creates a sequential, ordered {@code IntStream} that can be used for iteration or * further processing. *

+ * The range includes both {@code start} and {@code end}. + *

* Example Usage: *

{@code
      * RangeUtil.rangeClosed(3, 8).forEach(System.out::println);
@@ -129,9 +142,7 @@ public static IntStream range(int start, int end) {
      *
      * @param start the starting value of the range (inclusive)
      * @param end   upper-bound of the range (inclusive)
-     * @return an {@code IntStream} of integers from {@code 0} (inclusive) to
-     * {@code end} (inclusive)
-     * @throws IllegalArgumentException if the given {@code end} value is less equal to 0
+     * @return an {@code IntStream} of integers from {@code start} to {@code end} inclusive
      * @see IntStream
      */
     public static IntStream rangeClosed(int start, int end) {
@@ -139,28 +150,39 @@ public static IntStream rangeClosed(int start, int end) {
     }
 
     /**
-     * Generates a stream of integers starting from the specified {@code start} value, increment by
+     * Generates a stream of integers starting from the specified {@code start} value, incremented by
      * the specified {@code step}, up to the specified {@code end} value.
      * 

* It creates a sequential, ordered {@code IntStream} that can be used for iteration or * further processing. *

+ * The stream excludes the {@code end} value. + *

* Example Usage: *

{@code
-     * RangeUtil.range(3, 8, 2).forEach(System.out::println);
+     * RangeUtil.range(3, 10, 2).forEach(System.out::println);
      *
      * // Output:
      * // 3
      * // 5
      * // 7
+     * // 9
+     *
+     * RangeUtil.range(10, 3, -2).forEach(System.out::println);
+     *
+     * // Output:
+     * // 10
+     * // 8
+     * // 6
+     * // 4
      * }
* * @param start the starting value of the range (inclusive) * @param end upper-bound of the range (exclusive) - * @param step the increment (or decrement) between each value - * @return an {@code IntStream} of integers from {@code 0} (inclusive) to - * {@code end} (exclusive) - * @throws IllegalArgumentException if the given {@code end} value is less equal to 0 + * @param step the increment or decrement between each value (non-zero) + * @return an {@code IntStream} of integers from {@code start} to {@code end} exclusive stepping by {@code step} + * @throws IllegalArgumentException if {@code step} is zero or if {@code start} and {@code end} are inconsistent + * with the direction imposed by {@code step} * @see IntStream */ public static IntStream range(int start, int end, int step) { @@ -170,7 +192,7 @@ public static IntStream range(int start, int end, int step) { if ((step > 0 && start >= end) || (step < 0 && start <= end)) { throw new IllegalArgumentException("Range parameters are inconsistent with the step value."); } - return IntStream.iterate(start, (n) -> n < end, (n) -> n + step); + return IntStream.iterate(start, (n) -> step > 0 ? n < end : n > end, (n) -> n + step); } } diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/AesUtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/AesUtilTest.java new file mode 100644 index 0000000..c8390d1 --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/AesUtilTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; + +import static org.junit.jupiter.api.Assertions.*; + +class AesUtilTest { + + @Test + void testEncryptAndDecryptByte() throws GeneralSecurityException { + byte[] secretKey = "43f72073956d4c81".getBytes(StandardCharsets.UTF_8); + byte[] originalData = "Hello World".getBytes(StandardCharsets.UTF_8); + + byte[] encryptedData = AesUtil.encrypt(originalData, secretKey); + assertNotNull(encryptedData); + + byte[] decryptedData = AesUtil.decrypt(encryptedData, secretKey); + assertNotNull(decryptedData); + + assertArrayEquals(originalData, decryptedData); + } + + @Test + void testEncryptAndDecryptString() throws GeneralSecurityException { + var secret = "43f72073956d4c81"; + var originalData = "Hello World"; + + var encryptedData = AesUtil.encrypt(originalData, secret); + assertNotNull(encryptedData); + assertNotEquals(originalData, encryptedData); + + var decryptedData = AesUtil.decrypt(encryptedData, secret); + assertNotNull(decryptedData); + assertEquals(originalData, decryptedData); + } + + @Test + void testEncryptWithWrongKeyFails() throws GeneralSecurityException { + var secret = "43f72073956d4c81"; + var wrongSecret = "0000000000000000"; + var originalData = "Hello World"; + + var encryptedData = AesUtil.encrypt(originalData.getBytes(StandardCharsets.UTF_8), + secret.getBytes(StandardCharsets.UTF_8)); + assertNotNull(encryptedData); + + // When decrypting with the wrong key, a BadPaddingException or IllegalBlockSizeException is expected to be thrown + assertThrows(GeneralSecurityException.class, () -> { + AesUtil.decrypt(encryptedData, wrongSecret.getBytes(StandardCharsets.UTF_8)); + }); + } + + @Test + void testGenerateRandomSecret() { + var randomSecret = AesUtil.generateRandomSecret(); + assertNotNull(randomSecret); + assertEquals(16, randomSecret.length()); + } +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/Base64UtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/Base64UtilTest.java new file mode 100644 index 0000000..aec54fb --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/Base64UtilTest.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; + +public class Base64UtilTest { + + @Test + void testEncodeAndDecodeWithUtf8() { + var original = "Hello, Base64!"; + var encoded = Base64Util.encode(original); + assertNotNull(encoded); + assertNotEquals(original, encoded); + + var decoded = Base64Util.decode(encoded); + assertNotNull(decoded); + assertEquals(original, decoded); + } + + @Test + void testEncodeAndDecodeWithCharset() { + var original = "编码测试"; // Some unicode characters (Chinese) + var charset = StandardCharsets.UTF_8; + + var encoded = Base64Util.encode(original, charset); + assertNotNull(encoded); + assertNotEquals(original, encoded); + + var decoded = Base64Util.decode(encoded, charset); + assertNotNull(decoded); + assertEquals(original, decoded); + } + + @Test + void testEncodeUrlComponentsAndDecodeWithUtf8() { + var original = "This is a test for URL-safe Base64 encoding+!"; + + var encodedUrl = Base64Util.encodeUrlComponents(original); + assertNotNull(encodedUrl); + assertNotEquals(original, encodedUrl); + // URL-safe encoding should not contain '+' or '/' characters + assertFalse(encodedUrl.contains("+")); + assertFalse(encodedUrl.contains("/")); + + var decodedUrl = Base64Util.decodeUrlComponents(encodedUrl); + assertNotNull(decodedUrl); + assertEquals(original, decodedUrl); + } + + @Test + void testEncodeUrlComponentsAndDecodeWithCharset() { + var original = "测试 URL 安全编码"; // Unicode string + var charset = StandardCharsets.UTF_8; + + var encodedUrl = Base64Util.encodeUrlComponents(original, charset); + assertNotNull(encodedUrl); + assertNotEquals(original, encodedUrl); + + var decodedUrl = Base64Util.decodeUrlComponents(encodedUrl, charset); + assertNotNull(decodedUrl); + assertEquals(original, decodedUrl); + } + + @Test + void testEncodeAndDecodeEmptyString() { + var original = ""; + + var encoded = Base64Util.encode(original); + assertNotNull(encoded); + assertEquals("", Base64Util.decode(encoded)); + } + + @Test + void testEncodeAndDecodeNullSafety() { + // Since Base64Util does not explicitly handle null, the test expects NPE if null is input + assertThrows(NullPointerException.class, () -> Base64Util.encode(null)); + assertThrows(NullPointerException.class, () -> Base64Util.decode(null)); + } + +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/BoolUtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/BoolUtilTest.java new file mode 100644 index 0000000..23d38de --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/BoolUtilTest.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Test; + +import java.util.function.BooleanSupplier; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class BoolUtilTest { + + // Tests for and(Boolean... values) + + @Test + void and_AllTrueValues_ReturnsTrue() { + assertTrue(BoolUtil.and(true, true, true)); + } + + @Test + void and_SomeFalseValues_ReturnsFalse() { + assertFalse(BoolUtil.and(true, false, true)); + } + + @Test + void and_AllFalseValues_ReturnsFalse() { + assertFalse(BoolUtil.and(false, false)); + } + + @Test + void and_WithNullValues_IgnoresNulls() { + assertTrue(BoolUtil.and(true, null, true)); + assertFalse(BoolUtil.and(true, null, false)); + } + + @Test + void and_AllNullValues_ReturnsTrue() { + // Stream after filtering null is empty, allMatch on empty returns true + assertTrue(BoolUtil.and((Boolean) null, null)); + } + + // Tests for and(BooleanSupplier... valueSuppliers) + + @Test + void and_AllSuppliersTrue_ReturnsTrue() { + BooleanSupplier trueSupplier = () -> true; + BooleanSupplier falseSupplier = () -> false; + + assertTrue(BoolUtil.and(trueSupplier, trueSupplier)); + assertFalse(BoolUtil.and(trueSupplier, falseSupplier)); + } + + @Test + void and_WithNullSuppliers_IgnoresNull() { + BooleanSupplier trueSupplier = () -> true; + + assertTrue(BoolUtil.and(trueSupplier, null, trueSupplier)); + assertFalse(BoolUtil.and(trueSupplier, null, () -> false)); + } + + @Test + void and_AllNullSuppliers_ReturnsTrue() { + assertTrue(BoolUtil.and((BooleanSupplier) null, null)); + } + + + // Tests for or(Boolean... values) + + @Test + void or_AllTrueValues_ReturnsTrue() { + assertTrue(BoolUtil.or(true, true, true)); + } + + @Test + void or_SomeTrueValues_ReturnsTrue() { + assertTrue(BoolUtil.or(false, true, false)); + } + + @Test + void or_AllFalseValues_ReturnsFalse() { + assertFalse(BoolUtil.or(false, false)); + } + + @Test + void or_WithNullValues_IgnoresNull() { + assertTrue(BoolUtil.or(false, null, true)); + assertFalse(BoolUtil.or(false, null, false)); + } + + @Test + void or_AllNullValues_ReturnsFalse() { + // Stream after filtering null is empty, anyMatch on empty returns false + assertFalse(BoolUtil.or((Boolean) null, null)); + } + + // Tests for or(BooleanSupplier... valueSuppliers) + + @Test + void or_AllSuppliersTrue_ReturnsTrue() { + BooleanSupplier trueSupplier = () -> true; + BooleanSupplier falseSupplier = () -> false; + + assertTrue(BoolUtil.or(trueSupplier, trueSupplier)); + assertTrue(BoolUtil.or(falseSupplier, trueSupplier)); + assertFalse(BoolUtil.or(falseSupplier, falseSupplier)); + } + + @Test + void or_WithNullSuppliers_IgnoresNull() { + BooleanSupplier trueSupplier = () -> true; + BooleanSupplier falseSupplier = () -> false; + + assertTrue(BoolUtil.or(falseSupplier, null, trueSupplier)); + assertFalse(BoolUtil.or(falseSupplier, null, falseSupplier)); + } + + @Test + void or_AllNullSuppliers_ReturnsFalse() { + assertFalse(BoolUtil.or((BooleanSupplier) null, null)); + } +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/BranchUtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/BranchUtilTest.java new file mode 100644 index 0000000..05a2f14 --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/BranchUtilTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BooleanSupplier; + +import static org.junit.jupiter.api.Assertions.*; + +class BranchUtilTest { + + // Test the static methods or(Boolean... values) and and(Boolean... values) + @Test + void testOrWithBooleanValues() { + BranchUtil trueResult = BranchUtil.or(true, false, false); + assertNotNull(trueResult); + + BranchUtil falseResult = BranchUtil.or(false, false, false); + assertNotNull(falseResult); + } + + @Test + void testAndWithBooleanValues() { + BranchUtil trueResult = BranchUtil.and(true, true, true); + assertNotNull(trueResult); + + BranchUtil falseResult = BranchUtil.and(true, false, true); + assertNotNull(falseResult); + } + + // Test the static methods or(BooleanSupplier... valueSuppliers) and and(BooleanSupplier... valueSuppliers) + @Test + void testOrWithBooleanSuppliers() { + BooleanSupplier trueSupplier = () -> true; + BooleanSupplier falseSupplier = () -> false; + + BranchUtil trueResult = BranchUtil.or(falseSupplier, trueSupplier); + + BranchUtil falseResult = BranchUtil.or(falseSupplier, falseSupplier); + } + + @Test + void testAndWithBooleanSuppliers() { + BooleanSupplier trueSupplier = () -> true; + BooleanSupplier falseSupplier = () -> false; + + BranchUtil trueResult = BranchUtil.and(trueSupplier, trueSupplier); + + BranchUtil falseResult = BranchUtil.and(trueSupplier, falseSupplier); + } + + // Test thenSupply(T, T) + @Test + void testThenSupplyBothSuppliers_ResultTrue() { + BranchUtil b = BranchUtil.and(true); + String trueVal = "yes"; + String falseVal = "no"; + + String result = b.thenSupply(() -> trueVal, () -> falseVal); + assertEquals(trueVal, result); + } + + @Test + void testThenSupplyBothSuppliers_ResultFalse_WithFalseSupplier() { + BranchUtil b = BranchUtil.and(false); + String trueVal = "yes"; + String falseVal = "no"; + + String result = b.thenSupply(() -> trueVal, () -> falseVal); + assertEquals(falseVal, result); + } + + @Test + void testThenSupplyBothSuppliers_ResultFalse_NoFalseSupplier() { + BranchUtil b = BranchUtil.and(false); + String trueVal = "yes"; + + String result = b.thenSupply(() -> trueVal, null); + assertNull(result); + } + + @Test + void testThenSupplySingleTrueSupplier_ResultTrue() { + BranchUtil b = BranchUtil.and(true); + String trueVal = "success"; + + String result = b.thenSupply(() -> trueVal); + assertEquals(trueVal, result); + } + + @Test + void testThenSupplySingleTrueSupplier_ResultFalse() { + BranchUtil b = BranchUtil.and(false); + String trueVal = "success"; + + String result = b.thenSupply(() -> trueVal); + assertNull(result); + } + + // Test then(Runnable, Runnable) + @Test + void testThenWithBothHandlers_ResultTrue() { + BranchUtil b = BranchUtil.and(true); + AtomicBoolean trueRun = new AtomicBoolean(false); + AtomicBoolean falseRun = new AtomicBoolean(false); + + b.then(() -> trueRun.set(true), () -> falseRun.set(true)); + + assertTrue(trueRun.get()); + assertFalse(falseRun.get()); + } + + @Test + void testThenWithBothHandlers_ResultFalse() { + BranchUtil b = BranchUtil.and(false); + AtomicBoolean trueRun = new AtomicBoolean(false); + AtomicBoolean falseRun = new AtomicBoolean(false); + + b.then(() -> trueRun.set(true), () -> falseRun.set(true)); + + assertFalse(trueRun.get()); + assertTrue(falseRun.get()); + } + + @Test + void testThenWithOnlyTrueHandler_ResultTrue() { + BranchUtil b = BranchUtil.and(true); + AtomicBoolean trueRun = new AtomicBoolean(false); + + b.then(() -> trueRun.set(true)); + + assertTrue(trueRun.get()); + } + + @Test + void testThenWithOnlyTrueHandler_ResultFalse() { + BranchUtil b = BranchUtil.and(false); + AtomicBoolean trueRun = new AtomicBoolean(false); + + b.then(() -> trueRun.set(true)); + + assertFalse(trueRun.get()); + } +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/CollectionUtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/CollectionUtilTest.java new file mode 100644 index 0000000..4c7f197 --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/CollectionUtilTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Test; + +import java.util.*; +import java.util.function.Supplier; + +import static org.junit.jupiter.api.Assertions.*; + +class CollectionUtilTest { + + @Test + void chunk_NullOriginalCollection_ThrowsException() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> CollectionUtil.chunk(null, 3, ArrayList::new)); + assertEquals("Collection must not be null.", ex.getMessage()); + } + + @Test + void chunk_NegativeMaxSize_ThrowsException() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> CollectionUtil.chunk(List.of(1, 2), -1, ArrayList::new)); + assertEquals("maxSize must greater than 0.", ex.getMessage()); + } + + @Test + void chunk_NullCollectionFactory_ThrowsException() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> CollectionUtil.chunk(List.of(1, 2), 2, null)); + assertEquals("Factory method cannot be null.", ex.getMessage()); + } + + @Test + void chunk_EmptyCollection_ReturnsOneEmptySubCollection() { + List> chunks = CollectionUtil.chunk(Collections.emptyList(), 3, ArrayList::new); + assertEquals(1, chunks.size()); + assertTrue(chunks.get(0).isEmpty()); + } + + @Test + void chunk_CollectionSizeLessThanMaxSize_ReturnsOneSubCollectionWithAllElements() { + List list = List.of(1, 2); + List> chunks = CollectionUtil.chunk(list, 5, ArrayList::new); + assertEquals(1, chunks.size()); + assertEquals(list, chunks.get(0)); + } + + @Test + void chunk_CollectionSizeEqualMaxSize_ReturnsOneSubCollectionWithAllElements() { + List list = List.of(1, 2, 3); + List> chunks = CollectionUtil.chunk(list, 3, ArrayList::new); + assertEquals(1, chunks.size()); + assertEquals(list, chunks.get(0)); + } + + @Test + void chunk_CollectionSizeGreaterThanMaxSize_ReturnsMultipleSubCollections() { + List list = List.of(1, 2, 3, 4, 5, 6, 7); + int maxSize = 3; + List> chunks = CollectionUtil.chunk(list, maxSize, ArrayList::new); + + // Expect 3 subcollections: [1,2,3], [4,5,6], [7] + assertEquals(3, chunks.size()); + assertEquals(List.of(1, 2, 3), chunks.get(0)); + assertEquals(List.of(4, 5, 6), chunks.get(1)); + assertEquals(List.of(7), chunks.get(2)); + } + + @Test + void chunk_UsesDifferentCollectionTypeAsSubCollections() { + LinkedList list = new LinkedList<>(List.of(1, 2, 3, 4)); + Supplier> factory = LinkedList::new; + List> chunks = CollectionUtil.chunk(list, 2, factory); + assertEquals(2, chunks.size()); + assertInstanceOf(LinkedList.class, chunks.get(0)); + assertInstanceOf(LinkedList.class, chunks.get(1)); + assertEquals(List.of(1, 2), chunks.get(0)); + assertEquals(List.of(3, 4), chunks.get(1)); + } + + @Test + void chunk_CollectionWithOneElementAndMaxSizeOne_ReturnsOneSubCollection() { + List list = List.of("a"); + List> chunks = CollectionUtil.chunk(list, 1, ArrayList::new); + assertEquals(1, chunks.size()); + assertEquals(list, chunks.get(0)); + } + + @Test + void chunk_MaxSizeZero_ThrowsException() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> CollectionUtil.chunk(List.of(1), 0, ArrayList::new)); + assertEquals("maxSize must greater than 0.", ex.getMessage()); + } +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/HashUtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/HashUtilTest.java new file mode 100644 index 0000000..6b9cb6b --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/HashUtilTest.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class HashUtilTest { + + // Test MD2 hashing with explicit charset and default charset + @Test + void testMd2() { + String input = "test"; + // Known MD2 hash of "test" with UTF-8 + String expectedHash = "dd34716876364a02d0195e2fb9ae2d1b"; + assertEquals(expectedHash, HashUtil.md2(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.md2(input)); + // Test null charset fallback to UTF-8 + assertEquals(expectedHash, HashUtil.md2(input, null)); + } + + // Test MD5 hashing with explicit charset and default charset + @Test + void testMd5() { + String input = "test"; + // Known MD5 hash of "test" + String expectedHash = "098f6bcd4621d373cade4e832627b4f6"; + assertEquals(expectedHash, HashUtil.md5(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.md5(input)); + assertEquals(expectedHash, HashUtil.md5(input, null)); + } + + // Test SHA-1 hashing with explicit charset and default charset + @Test + void testSha1() { + String input = "test"; + // Known SHA-1 hash of "test" + String expectedHash = "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"; + assertEquals(expectedHash, HashUtil.sha1(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.sha1(input)); + assertEquals(expectedHash, HashUtil.sha1(input, null)); + } + + // Test SHA-224 hashing with explicit charset and default charset + @Test + void testSha224() { + String input = "test"; + // Known SHA-224 hash of "test" + String expectedHash = "90a3ed9e32b2aaf4c61c410eb925426119e1a9dc53d4286ade99a809"; + assertEquals(expectedHash, HashUtil.sha224(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.sha224(input)); + assertEquals(expectedHash, HashUtil.sha224(input, null)); + } + + // Test SHA-256 hashing with explicit charset and default charset + @Test + void testSha256() { + String input = "test"; + // Known SHA-256 hash of "test" + String expectedHash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"; + assertEquals(expectedHash, HashUtil.sha256(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.sha256(input)); + assertEquals(expectedHash, HashUtil.sha256(input, null)); + } + + // Test SHA-384 hashing with explicit charset and default charset + @Test + void testSha384() { + String input = "test"; + // Known SHA-384 hash of "test" + String expectedHash = "768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9"; + assertEquals(expectedHash, HashUtil.sha384(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.sha384(input)); + assertEquals(expectedHash, HashUtil.sha384(input, null)); + } + + // Test SHA-512 hashing with explicit charset and default charset + @Test + void testSha512() { + String input = "test"; + // Known SHA-512 hash of "test" + String expectedHash = "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"; + // remove all whitespace in expected to match format generated + expectedHash = expectedHash.replaceAll("\\s+", ""); + assertEquals(expectedHash, HashUtil.sha512(input, StandardCharsets.UTF_8)); + assertEquals(expectedHash, HashUtil.sha512(input)); + assertEquals(expectedHash, HashUtil.sha512(input, null)); + } + + // Test empty string input + @Test + void testEmptyString() { + String input = ""; + // MD5 hash of empty string + String expectedMd5 = "d41d8cd98f00b204e9800998ecf8427e"; + assertEquals(expectedMd5, HashUtil.md5(input)); + // SHA-256 hash of empty string + String expectedSha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + assertEquals(expectedSha256, HashUtil.sha256(input)); + } + + // Test null charset fallback for one algorithm as a sample + @Test + void testNullCharsetFallsBackToUtf8() { + String input = "abc"; + String hashWithNull = HashUtil.md5(input, null); + String hashWithUtf8 = HashUtil.md5(input, StandardCharsets.UTF_8); + assertEquals(hashWithUtf8, hashWithNull); + } +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/RangeUtilTest.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/RangeUtilTest.java new file mode 100644 index 0000000..13db7fb --- /dev/null +++ b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/RangeUtilTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.devkit.utils; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RangeUtilTest { + + /** + * Tests generating ascending range from 0 up to end (exclusive). + */ + @Test + void testRangeEndValid() { + int[] expected = {0, 1, 2, 3, 4}; + assertArrayEquals(expected, RangeUtil.range(5).toArray()); + } + + /** + * Tests that range(end) throws IllegalArgumentException for end less than or equal to zero. + */ + @Test + void testRangeEndInvalidThrows() { + IllegalArgumentException ex1 = assertThrows(IllegalArgumentException.class, + () -> RangeUtil.range(0)); + assertTrue(ex1.getMessage().contains("should not be less than or equal to 0")); + + IllegalArgumentException ex2 = assertThrows(IllegalArgumentException.class, + () -> RangeUtil.range(-3)); + assertTrue(ex2.getMessage().contains("should not be less than or equal to 0")); + } + + /** + * Tests ascending range where start is less than end. + */ + @Test + void testRangeStartEndAscending() { + int[] expected = {3, 4, 5, 6, 7}; + assertArrayEquals(expected, RangeUtil.range(3, 8).toArray()); + } + + /** + * Tests descending range where start is greater than end. + */ + @Test + void testRangeStartEndDescending() { + int[] expected = {8, 7, 6, 5, 4}; + assertArrayEquals(expected, RangeUtil.range(8, 3).toArray()); + } + + /** + * Tests empty stream when start equals end. + */ + @Test + void testRangeStartEqualsEndReturnsEmpty() { + assertEquals(0, RangeUtil.range(5, 5).count()); + } + + /** + * Tests that rangeClosed generates inclusive range in ascending order. + */ + @Test + void testRangeClosedAscending() { + int[] expected = {3, 4, 5, 6, 7, 8}; + assertArrayEquals(expected, RangeUtil.rangeClosed(3, 8).toArray()); + } + + /** + * Tests range method with positive step generating ascending sequence. + */ + @Test + void testRangeWithPositiveStep() { + int[] expected = {2, 4, 6, 8}; + assertArrayEquals(expected, RangeUtil.range(2, 10, 2).toArray()); + } + + /** + * Tests range method with negative step generating descending sequence. + */ + @Test + void testRangeWithNegativeStep() { + int[] expected = {10, 7, 4, 1}; + assertArrayEquals(expected, RangeUtil.range(10, 0, -3).toArray()); + } + + /** + * Tests that passing zero step throws IllegalArgumentException. + */ + @Test + void testRangeStepZeroThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> RangeUtil.range(0, 10, 0)); + assertEquals("Step value must not be zero.", ex.getMessage()); + } + + /** + * Tests that range with positive step but invalid start/end throws IllegalArgumentException. + */ + @Test + void testRangePositiveStepInvalidRangeThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> RangeUtil.range(10, 5, 1)); + assertEquals("Range parameters are inconsistent with the step value.", ex.getMessage()); + } + + /** + * Tests that range with negative step but invalid start/end throws IllegalArgumentException. + */ + @Test + void testRangeNegativeStepInvalidRangeThrows() { + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> RangeUtil.range(5, 10, -1)); + assertEquals("Range parameters are inconsistent with the step value.", ex.getMessage()); + } +} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/TestAesUtil.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/TestAesUtil.java deleted file mode 100644 index 39843ba..0000000 --- a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/TestAesUtil.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024-2025 OnixByte. - * - * 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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.devkit.utils; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TestAesUtil { - - private final static Logger log = LoggerFactory.getLogger(TestAesUtil.class); - - @Test - public void testGenerateRandomSecret() { - log.info("Secret is {}", AesUtil.generateRandomSecret()); - } - - @Test - public void testEncrypt() { - var secret = "43f72073956d4c81"; - - Assertions.assertEquals("IbbYZu8GtMruBURfMBVy/w==", AesUtil.encrypt("Hello World", secret)); - Assertions.assertEquals("1eVA7oQpTIhI7jc+6cdkmg==", AesUtil.encrypt("OnixByte", secret)); - Assertions.assertEquals("fk6oNRJK8a+Pz7zVwtlD0UQocq5c3GkRuem0Z6jdAN8=", AesUtil.encrypt("Welcome to use JDevKit!", secret)); - Assertions.assertEquals("dqzGjawNcQdBpXJWk/08UQ==", AesUtil.encrypt("127.0.0.1", secret)); - Assertions.assertEquals("uwQQI60yAGL91q9jCDgoeA==", AesUtil.encrypt("root", secret)); - } - - @Test - public void testDecrypt() { - var secret = "43f72073956d4c81"; - - Assertions.assertEquals("Hello World", AesUtil.decrypt("IbbYZu8GtMruBURfMBVy/w==", secret)); - Assertions.assertEquals("OnixByte", AesUtil.decrypt("1eVA7oQpTIhI7jc+6cdkmg==", secret)); - Assertions.assertEquals("Welcome to use JDevKit!", AesUtil.decrypt("fk6oNRJK8a+Pz7zVwtlD0UQocq5c3GkRuem0Z6jdAN8=", secret)); - Assertions.assertEquals("127.0.0.1", AesUtil.decrypt("dqzGjawNcQdBpXJWk/08UQ==", secret)); - Assertions.assertEquals("root", AesUtil.decrypt("uwQQI60yAGL91q9jCDgoeA==", secret)); - } - -} diff --git a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/TestBase64Util.java b/devkit-utils/src/test/java/com/onixbyte/devkit/utils/TestBase64Util.java deleted file mode 100644 index 6bbc3b1..0000000 --- a/devkit-utils/src/test/java/com/onixbyte/devkit/utils/TestBase64Util.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (C) 2024-2025 OnixByte. - * - * 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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.onixbyte.devkit.utils; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class TestBase64Util { - - private final static Logger log = LoggerFactory.getLogger(TestBase64Util.class); - - @Test - public void testEncode() { - Assertions.assertEquals("SGVsbG8gV29ybGQ=", Base64Util.encode("Hello World")); - Assertions.assertEquals("MTI3LjAuMC4x", Base64Util.encode("127.0.0.1")); - Assertions.assertEquals("cm9vdA==", Base64Util.encode("root")); - } - - @Test - public void testDecode() { - Assertions.assertEquals("Hello World", Base64Util.decode("SGVsbG8gV29ybGQ=")); - Assertions.assertEquals("127.0.0.1", Base64Util.decode("MTI3LjAuMC4x")); - Assertions.assertEquals("root", Base64Util.decode("cm9vdA==")); - } - - @Test - public void testEncodeUriComponent() { - Assertions.assertEquals("aHR0cHM6Ly9nb29nbGUuY29t", Base64Util.encodeUrlComponents("https://google.com")); - Assertions.assertEquals("aHR0cDovLzEyNy4wLjAuMTo4MDgwL2FwaS91c2VyLzEyMzQ1", Base64Util.encodeUrlComponents("http://127.0.0.1:8080/api/user/12345")); - } - - @Test - public void testDecodeUriComponent() { - Assertions.assertEquals("https://google.com", Base64Util.decodeUrlComponents("aHR0cHM6Ly9nb29nbGUuY29t")); - Assertions.assertEquals("http://127.0.0.1:8080/api/user/12345", Base64Util.decodeUrlComponents("aHR0cDovLzEyNy4wLjAuMTo4MDgwL2FwaS91c2VyLzEyMzQ1")); - } - -} diff --git a/guid/src/main/java/com/onixbyte/guid/impl/SequentialUuidCreator.java b/guid/src/main/java/com/onixbyte/guid/impl/SequentialUuidCreator.java new file mode 100644 index 0000000..309837d --- /dev/null +++ b/guid/src/main/java/com/onixbyte/guid/impl/SequentialUuidCreator.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2024-2025 OnixByte. + * + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.onixbyte.guid.impl; + +import com.onixbyte.guid.GuidCreator; + +import java.nio.ByteBuffer; +import java.security.SecureRandom; +import java.util.UUID; + +/** + * A {@code SequentialUuidCreator} is responsible for generating UUIDs following the UUID version 7 specification, which + * combines a timestamp with random bytes to create time-ordered unique identifiers. + *

+ * This implementation utilises a cryptographically strong {@link SecureRandom} instance to produce the random + * component of the UUID. The first 6 bytes of the UUID encode the current timestamp in milliseconds, ensuring that + * generated UUIDs are roughly ordered by creation time. + *

+ * The generated UUID adheres strictly to the layout and variant bits of UUID version 7 as defined in the specification. + *

+ * + * @implNote This class implements the {@link GuidCreator} interface, providing UUID instances as unique identifiers. + */ +public class SequentialUuidCreator implements GuidCreator { + + private final SecureRandom random; + + /** + * Constructs a new {@code SequentialUuidCreator} with its own {@link SecureRandom} instance. + */ + public SequentialUuidCreator() { + this.random = new SecureRandom(); + } + + /** + * Generates and returns the next UUID version 7 identifier. + * + * @return a {@link UUID} instance representing a unique, time-ordered identifier + */ + @Override + public UUID nextId() { + var value = randomBytes(); + var buf = ByteBuffer.wrap(value); + var high = buf.getLong(); + var low = buf.getLong(); + return new UUID(high, low); + } + + /** + * Produces a byte array representation of a UUID version 7, + * combining the current timestamp with random bytes. + * + * @return a 16-byte array conforming to UUIDv7 layout and variant bits + */ + private byte[] randomBytes() { + var value = new byte[16]; + random.nextBytes(value); + + var timestamp = ByteBuffer.allocate(Long.BYTES); + timestamp.putLong(System.currentTimeMillis()); + + System.arraycopy(timestamp.array(), 2, value, 0, 6); + + // Set version to 7 (UUIDv7) + value[6] = (byte) ((value[6] & 0x0F) | 0x70); + + // Set variant bits as per RFC 4122 + value[8] = (byte) ((value[8] & 0x3F) | 0x80); + + return value; + } +} diff --git a/guid/src/main/java/com/onixbyte/guid/impl/SnowflakeGuidCreator.java b/guid/src/main/java/com/onixbyte/guid/impl/SnowflakeGuidCreator.java index 7fab071..4df7203 100644 --- a/guid/src/main/java/com/onixbyte/guid/impl/SnowflakeGuidCreator.java +++ b/guid/src/main/java/com/onixbyte/guid/impl/SnowflakeGuidCreator.java @@ -19,8 +19,6 @@ import com.onixbyte.guid.GuidCreator; import com.onixbyte.guid.exceptions.TimingException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.time.LocalDateTime; import java.time.ZoneId; @@ -49,8 +47,6 @@ */ public final class SnowflakeGuidCreator implements GuidCreator { - private final static Logger log = LoggerFactory.getLogger(SnowflakeGuidCreator.class); - /** * Constructs a SnowflakeGuidGenerator with the default start epoch and custom worker ID, data * centre ID. diff --git a/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java b/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java index 2ae1b54..3578ec5 100644 --- a/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java +++ b/num4j/src/main/java/com/onixbyte/nums/PercentileCalculator.java @@ -72,10 +72,10 @@ private PercentileCalculator() { * @return a {@code Double} value representing the calculated percentile */ public static Double calculatePercentile(List values, Double percentile) { - var sorted = values.stream().sorted().toList(); - if (sorted.isEmpty()) { + if (values.isEmpty()) { throw new IllegalArgumentException("Unable to sort an empty list."); } + var sorted = values.stream().sorted().toList(); var rank = percentile / 100. * (sorted.size() - 1); var lowerIndex = (int) Math.floor(rank);