diff --git a/.travis.yml b/.travis.yml index 6cff0d3..582101f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ jdk: - oraclejdk8 script: - - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.exclusions="src/main/java/com/antkorwin/xsync/springframework/util/*" -Dsonar.issue.ignore.multicriteria="e1" -Dsonar.issue.ignore.multicriteria.e1.ruleKey="squid:S00119" -Dsonar.issue.ignore.multicriteria.e1.resourceKey="**/*.java" + - mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent package sonar:sonar -Dsonar.issue.ignore.multicriteria="e1" -Dsonar.issue.ignore.multicriteria.e1.ruleKey="squid:S00119" -Dsonar.issue.ignore.multicriteria.e1.resourceKey="**/*.java" addons: sonarcloud: diff --git a/pom.xml b/pom.xml index 62ef5ec..0d4211a 100755 --- a/pom.xml +++ b/pom.xml @@ -52,6 +52,12 @@ 3.1.0 test + + org.springframework + spring-core + 5.0.7.RELEASE + provided + junit junit diff --git a/src/main/java/com/antkorwin/xsync/XMutex.java b/src/main/java/com/antkorwin/xsync/XMutex.java index 92cf629..5952154 100644 --- a/src/main/java/com/antkorwin/xsync/XMutex.java +++ b/src/main/java/com/antkorwin/xsync/XMutex.java @@ -12,7 +12,7 @@ @Getter public class XMutex { - private KeyT key; + private final KeyT key; public XMutex(KeyT key) { this.key = key; diff --git a/src/main/java/com/antkorwin/xsync/XMutexFactory.java b/src/main/java/com/antkorwin/xsync/XMutexFactory.java index a9feef2..b748bcf 100644 --- a/src/main/java/com/antkorwin/xsync/XMutexFactory.java +++ b/src/main/java/com/antkorwin/xsync/XMutexFactory.java @@ -1,7 +1,9 @@ package com.antkorwin.xsync; -import com.antkorwin.xsync.springframework.util.ConcurrentReferenceHashMap; + + +import org.springframework.util.ConcurrentReferenceHashMap; import java.util.concurrent.ConcurrentMap; @@ -51,7 +53,8 @@ public XMutexFactory(int concurrencyLevel, * then returns the same reference of the mutex. */ public XMutex getMutex(KeyT key) { - return this.map.compute(key, (k, v) -> (v == null) ? new XMutex<>(k) : v); + //return this.map.compute(key, (k, v) -> (v == null) ? new XMutex<>(k) : v); + return this.map.computeIfAbsent(key, XMutex::new); } /** diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/Assert.java b/src/main/java/com/antkorwin/xsync/springframework/util/Assert.java deleted file mode 100644 index 0fb76b2..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/Assert.java +++ /dev/null @@ -1,722 +0,0 @@ -/* - * Copyright 2002-2017 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.util.Collection; -import java.util.Map; -import java.util.function.Supplier; - -/** - * Assertion utility class that assists in validating arguments. - *

- *

Useful for identifying programmer errors early and clearly at runtime. - *

- *

For example, if the contract of a public method states it does not - * allow {@code null} arguments, {@code Assert} can be used to validate that - * contract. Doing this clearly indicates a contract violation when it - * occurs and protects the class's invariants. - *

- *

Typically used to validate method arguments rather than configuration - * properties, to check for cases that are usually programmer errors rather - * than configuration errors. In contrast to configuration initialization - * code, there is usually no point in falling back to defaults in such methods. - *

- *

This class is similar to JUnit's assertion library. If an argument value is - * deemed invalid, an {@link IllegalArgumentException} is thrown (typically). - * For example: - *

- *

- * Assert.notNull(clazz, "The class must not be null");
- * Assert.isTrue(i > 0, "The value must be greater than zero");
- *

- *

Mainly for internal use within the framework; consider - * Apache's Commons Lang - * for a more comprehensive suite of {@code String} utilities. - * - * @author Keith Donald - * @author Juergen Hoeller - * @author Sam Brannen - * @author Colin Sampaleanu - * @author Rob Harrop - * @since 1.1.2 - */ -public abstract class Assert { - - /** - * Assert a boolean expression, throwing an {@code IllegalStateException} - * if the expression evaluates to {@code false}. - *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} - * on an assertion failure. - *

-     * Assert.state(id == null,
-     * () -> "ID for " + entity.getName() + " must not already be initialized");
-     * 
- * - * @param expression a boolean expression - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalStateException if {@code expression} is {@code false} - * @since 5.0 - */ - public static void state(boolean expression, Supplier messageSupplier) { - if (!expression) { - throw new IllegalStateException(nullSafeGet(messageSupplier)); - } - } - - private static String nullSafeGet(Supplier messageSupplier) { - return (messageSupplier != null ? messageSupplier.get() : null); - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #state(boolean, String)} - */ - @Deprecated - public static void state(boolean expression) { - state(expression, "[Assertion failed] - this state invariant must be true"); - } - - /** - * Assert a boolean expression, throwing an {@code IllegalStateException} - * if the expression evaluates to {@code false}. - *

Call {@link #isTrue} if you wish to throw an {@code IllegalArgumentException} - * on an assertion failure. - *

Assert.state(id == null, "The id property must not already be initialized");
- * - * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * - * @throws IllegalStateException if {@code expression} is {@code false} - */ - public static void state(boolean expression, String message) { - if (!expression) { - throw new IllegalStateException(message); - } - } - - /** - * Assert a boolean expression, throwing an {@code IllegalArgumentException} - * if the expression evaluates to {@code false}. - *
-     * Assert.isTrue(i > 0, () -> "The value '" + i + "' must be greater than zero");
-     * 
- * - * @param expression a boolean expression - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if {@code expression} is {@code false} - * @since 5.0 - */ - public static void isTrue(boolean expression, Supplier messageSupplier) { - if (!expression) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #isTrue(boolean, String)} - */ - @Deprecated - public static void isTrue(boolean expression) { - isTrue(expression, "[Assertion failed] - this expression must be true"); - } - - /** - * Assert a boolean expression, throwing an {@code IllegalArgumentException} - * if the expression evaluates to {@code false}. - *
Assert.isTrue(i > 0, "The value must be greater than zero");
- * - * @param expression a boolean expression - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if {@code expression} is {@code false} - */ - public static void isTrue(boolean expression, String message) { - if (!expression) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is {@code null}. - *
-     * Assert.isNull(value, () -> "The value '" + value + "' must be null");
-     * 
- * - * @param object the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the object is not {@code null} - * @since 5.0 - */ - public static void isNull(Object object, Supplier messageSupplier) { - if (object != null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #isNull(Object, String)} - */ - @Deprecated - public static void isNull(Object object) { - isNull(object, "[Assertion failed] - the object argument must be null"); - } - - /** - * Assert that an object is {@code null}. - *
Assert.isNull(value, "The value must be null");
- * - * @param object the object to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the object is not {@code null} - */ - public static void isNull(Object object, String message) { - if (object != null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an object is not {@code null}. - *
-     * Assert.notNull(clazz, () -> "The class '" + clazz.getName() + "' must not be null");
-     * 
- * - * @param object the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the object is {@code null} - * @since 5.0 - */ - public static void notNull(Object object, Supplier messageSupplier) { - if (object == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #notNull(Object, String)} - */ - @Deprecated - public static void notNull(Object object) { - notNull(object, "[Assertion failed] - this argument is required; it must not be null"); - } - - /** - * Assert that an object is not {@code null}. - *
Assert.notNull(clazz, "The class must not be null");
- * - * @param object the object to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the object is {@code null} - */ - public static void notNull(Object object, String message) { - if (object == null) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given String is not empty; that is, - * it must not be {@code null} and not the empty String. - *
-     * Assert.hasLength(name, () -> "Name for account '" + account.getId() + "' must not be empty");
-     * 
- * - * @param text the String to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the text is empty - * @see StringUtils#hasLength - * @since 5.0 - */ - public static void hasLength(String text, Supplier messageSupplier) { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #hasLength(String, String)} - */ - @Deprecated - public static void hasLength(String text) { - hasLength(text, - "[Assertion failed] - this String argument must have length; it must not be null or empty"); - } - - /** - * Assert that the given String is not empty; that is, - * it must not be {@code null} and not the empty String. - *
Assert.hasLength(name, "Name must not be empty");
- * - * @param text the String to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the text is empty - * @see StringUtils#hasLength - */ - public static void hasLength(String text, String message) { - if (!StringUtils.hasLength(text)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given String contains valid text content; that is, it must not - * be {@code null} and must contain at least one non-whitespace character. - *
-     * Assert.hasText(name, () -> "Name for account '" + account.getId() + "' must not be empty");
-     * 
- * - * @param text the String to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the text does not contain valid text content - * @see StringUtils#hasText - * @since 5.0 - */ - public static void hasText(String text, Supplier messageSupplier) { - if (!StringUtils.hasText(text)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #hasText(String, String)} - */ - @Deprecated - public static void hasText(String text) { - hasText(text, - "[Assertion failed] - this String argument must have text; it must not be null, empty, or blank"); - } - - /** - * Assert that the given String contains valid text content; that is, it must not - * be {@code null} and must contain at least one non-whitespace character. - *
Assert.hasText(name, "'name' must not be empty");
- * - * @param text the String to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the text does not contain valid text content - * @see StringUtils#hasText - */ - public static void hasText(String text, String message) { - if (!StringUtils.hasText(text)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the given text does not contain the given substring. - *
Assert.doesNotContain(name, "rod", "Name must not contain 'rod'");
- * - * @param textToSearch the text to search - * @param substring the substring to find within the text - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the text contains the substring - */ - public static void doesNotContain(String textToSearch, String substring, String message) { - if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) && - textToSearch.contains(substring)) { - throw new IllegalArgumentException(message); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #doesNotContain(String, String, String)} - */ - @Deprecated - public static void doesNotContain(String textToSearch, String substring) { - doesNotContain(textToSearch, substring, - () -> "[Assertion failed] - this String argument must not contain the substring [" + substring + "]"); - } - - /** - * Assert that the given text does not contain the given substring. - *
-     * Assert.doesNotContain(name, forbidden, () -> "Name must not contain '" + forbidden + "'");
-     * 
- * - * @param textToSearch the text to search - * @param substring the substring to find within the text - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the text contains the substring - * @since 5.0 - */ - public static void doesNotContain(String textToSearch, String substring, Supplier messageSupplier) { - if (StringUtils.hasLength(textToSearch) && StringUtils.hasLength(substring) && - textToSearch.contains(substring)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * Assert that an array contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
-     * Assert.notEmpty(array, () -> "The " + arrayType + " array must contain elements");
-     * 
- * - * @param array the array to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the object array is {@code null} or contains no elements - * @since 5.0 - */ - public static void notEmpty(Object[] array, Supplier messageSupplier) { - if (ObjectUtils.isEmpty(array)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Object[], String)} - */ - @Deprecated - public static void notEmpty(Object[] array) { - notEmpty(array, "[Assertion failed] - this array must not be empty: it must contain at least 1 element"); - } - - /** - * Assert that an array contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
Assert.notEmpty(array, "The array must contain elements");
- * - * @param array the array to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the object array is {@code null} or contains no elements - */ - public static void notEmpty(Object[] array, String message) { - if (ObjectUtils.isEmpty(array)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that an array contains no {@code null} elements. - *

Note: Does not complain if the array is empty! - *

-     * Assert.noNullElements(array, () -> "The " + arrayType + " array must contain non-null elements");
-     * 
- * - * @param array the array to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the object array contains a {@code null} element - * @since 5.0 - */ - public static void noNullElements(Object[] array, Supplier messageSupplier) { - if (array != null) { - for (Object element : array) { - if (element == null) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #noNullElements(Object[], String)} - */ - @Deprecated - public static void noNullElements(Object[] array) { - noNullElements(array, "[Assertion failed] - this array must not contain any null elements"); - } - - /** - * Assert that an array contains no {@code null} elements. - *

Note: Does not complain if the array is empty! - *

Assert.noNullElements(array, "The array must contain non-null elements");
- * - * @param array the array to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the object array contains a {@code null} element - */ - public static void noNullElements(Object[] array, String message) { - if (array != null) { - for (Object element : array) { - if (element == null) { - throw new IllegalArgumentException(message); - } - } - } - } - - /** - * Assert that a collection contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
-     * Assert.notEmpty(collection, () -> "The " + collectionType + " collection must contain elements");
-     * 
- * - * @param collection the collection to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the collection is {@code null} or - * contains no elements - * @since 5.0 - */ - public static void notEmpty(Collection collection, Supplier messageSupplier) { - if (CollectionUtils.isEmpty(collection)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Collection, String)} - */ - @Deprecated - public static void notEmpty(Collection collection) { - notEmpty(collection, - "[Assertion failed] - this collection must not be empty: it must contain at least 1 element"); - } - - /** - * Assert that a collection contains elements; that is, it must not be - * {@code null} and must contain at least one element. - *
Assert.notEmpty(collection, "Collection must contain elements");
- * - * @param collection the collection to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the collection is {@code null} or - * contains no elements - */ - public static void notEmpty(Collection collection, String message) { - if (CollectionUtils.isEmpty(collection)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that a Map contains entries; that is, it must not be {@code null} - * and must contain at least one entry. - *
-     * Assert.notEmpty(map, () -> "The " + mapType + " map must contain entries");
-     * 
- * - * @param map the map to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails - * - * @throws IllegalArgumentException if the map is {@code null} or contains no entries - * @since 5.0 - */ - public static void notEmpty(Map map, Supplier messageSupplier) { - if (CollectionUtils.isEmpty(map)) { - throw new IllegalArgumentException(nullSafeGet(messageSupplier)); - } - } - - /** - * @deprecated as of 4.3.7, in favor of {@link #notEmpty(Map, String)} - */ - @Deprecated - public static void notEmpty(Map map) { - notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry"); - } - - /** - * Assert that a Map contains entries; that is, it must not be {@code null} - * and must contain at least one entry. - *
Assert.notEmpty(map, "Map must contain entries");
- * - * @param map the map to check - * @param message the exception message to use if the assertion fails - * - * @throws IllegalArgumentException if the map is {@code null} or contains no entries - */ - public static void notEmpty(Map map, String message) { - if (CollectionUtils.isEmpty(map)) { - throw new IllegalArgumentException(message); - } - } - - /** - * Assert that the provided object is an instance of the provided class. - *
-     * Assert.instanceOf(Foo.class, foo, () -> "Processing " + Foo.class.getSimpleName() + ":");
-     * 
- * - * @param type the type to check against - * @param obj the object to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails. See {@link #isInstanceOf(Class, Object, String)} for details. - * - * @throws IllegalArgumentException if the object is not an instance of type - * @since 5.0 - */ - public static void isInstanceOf(Class type, Object obj, Supplier messageSupplier) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - instanceCheckFailed(type, obj, nullSafeGet(messageSupplier)); - } - } - - private static void instanceCheckFailed(Class type, Object obj, String msg) { - String className = (obj != null ? obj.getClass().getName() : "null"); - String result = ""; - boolean defaultMessage = true; - if (StringUtils.hasLength(msg)) { - if (endsWithSeparator(msg)) { - result = msg + " "; - } else { - result = messageWithTypeName(msg, className); - defaultMessage = false; - } - } - if (defaultMessage) { - result = result + ("Object of class [" + className + "] must be an instance of " + type); - } - throw new IllegalArgumentException(result); - } - - private static boolean endsWithSeparator(String msg) { - return (msg.endsWith(":") || msg.endsWith(";") || msg.endsWith(",") || msg.endsWith(".")); - } - - private static String messageWithTypeName(String msg, Object typeName) { - return msg + (msg.endsWith(" ") ? "" : ": ") + typeName; - } - - /** - * Assert that the provided object is an instance of the provided class. - *
Assert.instanceOf(Foo.class, foo);
- * - * @param type the type to check against - * @param obj the object to check - * - * @throws IllegalArgumentException if the object is not an instance of type - */ - public static void isInstanceOf(Class type, Object obj) { - isInstanceOf(type, obj, ""); - } - - /** - * Assert that the provided object is an instance of the provided class. - *
Assert.instanceOf(Foo.class, foo, "Foo expected");
- * - * @param type the type to check against - * @param obj the object to check - * @param message a message which will be prepended to provide further context. - * If it is empty or ends in ":" or ";" or "," or ".", a full exception message - * will be appended. If it ends in a space, the name of the offending object's - * type will be appended. In any other case, a ":" with a space and the name - * of the offending object's type will be appended. - * - * @throws IllegalArgumentException if the object is not an instance of type - */ - public static void isInstanceOf(Class type, Object obj, String message) { - notNull(type, "Type to check against must not be null"); - if (!type.isInstance(obj)) { - instanceCheckFailed(type, obj, message); - } - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
-     * Assert.isAssignable(Number.class, myClass, () -> "Processing " + myAttributeName + ":");
-     * 
- * - * @param superType the super type to check against - * @param subType the sub type to check - * @param messageSupplier a supplier for the exception message to use if the - * assertion fails. See {@link #isAssignable(Class, Class, String)} for details. - * - * @throws IllegalArgumentException if the classes are not assignable - * @since 5.0 - */ - public static void isAssignable(Class superType, Class subType, Supplier messageSupplier) { - notNull(superType, "Super type to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - assignableCheckFailed(superType, subType, nullSafeGet(messageSupplier)); - } - } - - private static void assignableCheckFailed(Class superType, Class subType, String msg) { - String result = ""; - boolean defaultMessage = true; - if (StringUtils.hasLength(msg)) { - if (endsWithSeparator(msg)) { - result = msg + " "; - } else { - result = messageWithTypeName(msg, subType); - defaultMessage = false; - } - } - if (defaultMessage) { - result = result + (subType + " is not assignable to " + superType); - } - throw new IllegalArgumentException(result); - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
Assert.isAssignable(Number.class, myClass);
- * - * @param superType the super type to check - * @param subType the sub type to check - * - * @throws IllegalArgumentException if the classes are not assignable - */ - public static void isAssignable(Class superType, Class subType) { - isAssignable(superType, subType, ""); - } - - /** - * Assert that {@code superType.isAssignableFrom(subType)} is {@code true}. - *
Assert.isAssignable(Number.class, myClass, "Number expected");
- * - * @param superType the super type to check against - * @param subType the sub type to check - * @param message a message which will be prepended to provide further context. - * If it is empty or ends in ":" or ";" or "," or ".", a full exception message - * will be appended. If it ends in a space, the name of the offending sub type - * will be appended. In any other case, a ":" with a space and the name of the - * offending sub type will be appended. - * - * @throws IllegalArgumentException if the classes are not assignable - */ - public static void isAssignable(Class superType, Class subType, String message) { - notNull(superType, "Super type to check against must not be null"); - if (subType == null || !superType.isAssignableFrom(subType)) { - assignableCheckFailed(superType, subType, message); - } - } - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/ClassUtils.java b/src/main/java/com/antkorwin/xsync/springframework/util/ClassUtils.java deleted file mode 100644 index 2cf6844..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/ClassUtils.java +++ /dev/null @@ -1,1387 +0,0 @@ -/* - * Copyright 2002-2018 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.beans.Introspector; -import java.io.Closeable; -import java.io.Externalizable; -import java.io.Serializable; -import java.lang.reflect.*; -import java.util.*; - -/** - * Miscellaneous class utility methods. - * Mainly for internal use within the framework. - * - * @author Juergen Hoeller - * @author Keith Donald - * @author Rob Harrop - * @author Sam Brannen - * @see TypeUtils - * @see ReflectionUtils - * @since 1.1 - */ -public abstract class ClassUtils { - - /** - * Suffix for array class names: "[]" - */ - public static final String ARRAY_SUFFIX = "[]"; - /** - * The CGLIB class separator: "$$" - */ - public static final String CGLIB_CLASS_SEPARATOR = "$$"; - /** - * The ".class" file suffix - */ - public static final String CLASS_FILE_SUFFIX = ".class"; - /** - * Prefix for internal array class names: "[" - */ - private static final String INTERNAL_ARRAY_PREFIX = "["; - /** - * Prefix for internal non-primitive array class names: "[L" - */ - private static final String NON_PRIMITIVE_ARRAY_PREFIX = "[L"; - /** - * The package separator character: '.' - */ - private static final char PACKAGE_SEPARATOR = '.'; - /** - * The path separator character: '/' - */ - private static final char PATH_SEPARATOR = '/'; - /** - * The inner class separator character: '$' - */ - private static final char INNER_CLASS_SEPARATOR = '$'; - /** - * Map with primitive wrapper type as key and corresponding primitive - * type as value, for example: Integer.class -> int.class. - */ - private static final Map, Class> primitiveWrapperTypeMap = new IdentityHashMap<>(8); - - /** - * Map with primitive type as key and corresponding wrapper - * type as value, for example: int.class -> Integer.class. - */ - private static final Map, Class> primitiveTypeToWrapperMap = new IdentityHashMap<>(8); - - /** - * Map with primitive type name as key and corresponding primitive - * type as value, for example: "int" -> "int.class". - */ - private static final Map> primitiveTypeNameMap = new HashMap<>(32); - - /** - * Map with common Java language class name as key and corresponding Class as value. - * Primarily for efficient deserialization of remote invocations. - */ - private static final Map> commonClassCache = new HashMap<>(64); - - /** - * Common Java language interfaces which are supposed to be ignored - * when searching for 'primary' user-level interfaces. - */ - private static final Set> javaLanguageInterfaces; - - - static { - primitiveWrapperTypeMap.put(Boolean.class, boolean.class); - primitiveWrapperTypeMap.put(Byte.class, byte.class); - primitiveWrapperTypeMap.put(Character.class, char.class); - primitiveWrapperTypeMap.put(Double.class, double.class); - primitiveWrapperTypeMap.put(Float.class, float.class); - primitiveWrapperTypeMap.put(Integer.class, int.class); - primitiveWrapperTypeMap.put(Long.class, long.class); - primitiveWrapperTypeMap.put(Short.class, short.class); - - primitiveWrapperTypeMap.forEach((key, value) -> { - primitiveTypeToWrapperMap.put(value, key); - registerCommonClasses(key); - }); - - Set> primitiveTypes = new HashSet<>(32); - primitiveTypes.addAll(primitiveWrapperTypeMap.values()); - Collections.addAll(primitiveTypes, boolean[].class, byte[].class, char[].class, - double[].class, float[].class, int[].class, long[].class, short[].class); - primitiveTypes.add(void.class); - for (Class primitiveType : primitiveTypes) { - primitiveTypeNameMap.put(primitiveType.getName(), primitiveType); - } - - registerCommonClasses(Boolean[].class, Byte[].class, Character[].class, Double[].class, - Float[].class, Integer[].class, Long[].class, Short[].class); - registerCommonClasses(Number.class, Number[].class, String.class, String[].class, - Class.class, Class[].class, Object.class, Object[].class); - registerCommonClasses(Throwable.class, Exception.class, RuntimeException.class, - Error.class, StackTraceElement.class, StackTraceElement[].class); - registerCommonClasses(Enum.class, Iterable.class, Iterator.class, Enumeration.class, - Collection.class, List.class, Set.class, Map.class, Map.Entry.class, Optional.class); - - Class[] javaLanguageInterfaceArray = {Serializable.class, Externalizable.class, - Closeable.class, AutoCloseable.class, Cloneable.class, Comparable.class}; - registerCommonClasses(javaLanguageInterfaceArray); - javaLanguageInterfaces = new HashSet<>(Arrays.asList(javaLanguageInterfaceArray)); - } - - - /** - * Register the given common classes with the ClassUtils cache. - */ - private static void registerCommonClasses(Class... commonClasses) { - for (Class clazz : commonClasses) { - commonClassCache.put(clazz.getName(), clazz); - } - } - - /** - * Return the default ClassLoader to use: typically the thread context - * ClassLoader, if available; the ClassLoader that loaded the ClassUtils - * class will be used as fallback. - *

Call this method if you intend to use the thread context ClassLoader - * in a scenario where you clearly prefer a non-null ClassLoader reference: - * for example, for class path resource loading (but not necessarily for - * {@code Class.forName}, which accepts a {@code null} ClassLoader - * reference as well). - * - * @return the default ClassLoader (only {@code null} if even the system - * ClassLoader isn't accessible) - * @see Thread#getContextClassLoader() - * @see ClassLoader#getSystemClassLoader() - */ - public static ClassLoader getDefaultClassLoader() { - ClassLoader cl = null; - try { - cl = Thread.currentThread().getContextClassLoader(); - } catch (Throwable ex) { - // Cannot access thread context ClassLoader - falling back... - } - if (cl == null) { - // No thread context class loader -> use class loader of this class. - cl = ClassUtils.class.getClassLoader(); - if (cl == null) { - // getClassLoader() returning null indicates the bootstrap ClassLoader - try { - cl = ClassLoader.getSystemClassLoader(); - } catch (Throwable ex) { - // Cannot access system ClassLoader - oh well, maybe the caller can live with null... - } - } - } - return cl; - } - - /** - * Override the thread context ClassLoader with the environment's bean ClassLoader - * if necessary, i.e. if the bean ClassLoader is not equivalent to the thread - * context ClassLoader already. - * - * @param classLoaderToUse the actual ClassLoader to use for the thread context - * - * @return the original thread context ClassLoader, or {@code null} if not overridden - */ - public static ClassLoader overrideThreadContextClassLoader(ClassLoader classLoaderToUse) { - Thread currentThread = Thread.currentThread(); - ClassLoader threadContextClassLoader = currentThread.getContextClassLoader(); - if (classLoaderToUse != null && !classLoaderToUse.equals(threadContextClassLoader)) { - currentThread.setContextClassLoader(classLoaderToUse); - return threadContextClassLoader; - } else { - return null; - } - } - - /** - * Replacement for {@code Class.forName()} that also returns Class instances - * for primitives (e.g. "int") and array class names (e.g. "String[]"). - * Furthermore, it is also capable of resolving inner class names in Java source - * style (e.g. "java.lang.Thread.State" instead of "java.lang.Thread$State"). - * - * @param name the name of the Class - * @param classLoader the class loader to use - * (may be {@code null}, which indicates the default class loader) - * - * @return Class instance for the supplied name - * @throws ClassNotFoundException if the class was not found - * @throws LinkageError if the class file could not be loaded - * @see Class#forName(String, boolean, ClassLoader) - */ - public static Class forName(String name, ClassLoader classLoader) - throws ClassNotFoundException, LinkageError { - - Assert.notNull(name, "Name must not be null"); - - Class clazz = resolvePrimitiveClassName(name); - if (clazz == null) { - clazz = commonClassCache.get(name); - } - if (clazz != null) { - return clazz; - } - - // "java.lang.String[]" style arrays - if (name.endsWith(ARRAY_SUFFIX)) { - String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length()); - Class elementClass = forName(elementClassName, classLoader); - return Array.newInstance(elementClass, 0).getClass(); - } - - // "[Ljava.lang.String;" style arrays - if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) { - String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1); - Class elementClass = forName(elementName, classLoader); - return Array.newInstance(elementClass, 0).getClass(); - } - - // "[[I" or "[[Ljava.lang.String;" style arrays - if (name.startsWith(INTERNAL_ARRAY_PREFIX)) { - String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length()); - Class elementClass = forName(elementName, classLoader); - return Array.newInstance(elementClass, 0).getClass(); - } - - ClassLoader clToUse = classLoader; - if (clToUse == null) { - clToUse = getDefaultClassLoader(); - } - try { - return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name)); - } catch (ClassNotFoundException ex) { - int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR); - if (lastDotIndex != -1) { - String innerClassName = - name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1); - try { - return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName)); - } catch (ClassNotFoundException ex2) { - // Swallow - let original exception get through - } - } - throw ex; - } - } - - /** - * Resolve the given class name into a Class instance. Supports - * primitives (like "int") and array class names (like "String[]"). - *

This is effectively equivalent to the {@code forName} - * method with the same arguments, with the only difference being - * the exceptions thrown in case of class loading failure. - * - * @param className the name of the Class - * @param classLoader the class loader to use - * (may be {@code null}, which indicates the default class loader) - * - * @return Class instance for the supplied name - * @throws IllegalArgumentException if the class name was not resolvable - * (that is, the class could not be found or the class file could not be loaded) - * @see #forName(String, ClassLoader) - */ - public static Class resolveClassName(String className, ClassLoader classLoader) - throws IllegalArgumentException { - - try { - return forName(className, classLoader); - } catch (ClassNotFoundException ex) { - throw new IllegalArgumentException("Could not find class [" + className + "]", ex); - } catch (LinkageError err) { - throw new IllegalArgumentException("Unresolvable class definition for class [" + className + "]", err); - } - } - - /** - * Determine whether the {@link Class} identified by the supplied name is present - * and can be loaded. Will return {@code false} if either the class or - * one of its dependencies is not present or cannot be loaded. - * - * @param className the name of the class to check - * @param classLoader the class loader to use - * (may be {@code null} which indicates the default class loader) - * - * @return whether the specified class is present - */ - public static boolean isPresent(String className, ClassLoader classLoader) { - try { - forName(className, classLoader); - return true; - } catch (Throwable ex) { - // Class or one of its dependencies is not present... - return false; - } - } - - /** - * Check whether the given class is cache-safe in the given context, - * i.e. whether it is loaded by the given ClassLoader or a parent of it. - * - * @param clazz the class to analyze - * @param classLoader the ClassLoader to potentially cache metadata in - * (may be {@code null} which indicates the system class loader) - */ - public static boolean isCacheSafe(Class clazz, ClassLoader classLoader) { - Assert.notNull(clazz, "Class must not be null"); - try { - ClassLoader target = clazz.getClassLoader(); - // Common cases - if (target == classLoader || target == null) { - return true; - } - if (classLoader == null) { - return false; - } - // Check for match in ancestors -> positive - ClassLoader current = classLoader; - while (current != null) { - current = current.getParent(); - if (current == target) { - return true; - } - } - // Check for match in children -> negative - while (target != null) { - target = target.getParent(); - if (target == classLoader) { - return false; - } - } - } catch (SecurityException ex) { - // Fall through to loadable check below - } - - // Fallback for ClassLoaders without parent/child relationship: - // safe if same Class can be loaded from given ClassLoader - return (classLoader != null && isLoadable(clazz, classLoader)); - } - - /** - * Check whether the given class is loadable in the given ClassLoader. - * - * @param clazz the class to check (typically an interface) - * @param classLoader the ClassLoader to check against - * - * @since 5.0.6 - */ - private static boolean isLoadable(Class clazz, ClassLoader classLoader) { - try { - return (clazz == classLoader.loadClass(clazz.getName())); - // Else: different class with same name found - } catch (ClassNotFoundException ex) { - // No corresponding class found at all - return false; - } - } - - /** - * Resolve the given class name as primitive class, if appropriate, - * according to the JVM's naming rules for primitive classes. - *

Also supports the JVM's internal class names for primitive arrays. - * Does not support the "[]" suffix notation for primitive arrays; - * this is only supported by {@link #forName(String, ClassLoader)}. - * - * @param name the name of the potentially primitive class - * - * @return the primitive class, or {@code null} if the name does not denote - * a primitive class or primitive array class - */ - public static Class resolvePrimitiveClassName(String name) { - Class result = null; - // Most class names will be quite long, considering that they - // SHOULD sit in a package, so a length check is worthwhile. - if (name != null && name.length() <= 8) { - // Could be a primitive - likely. - result = primitiveTypeNameMap.get(name); - } - return result; - } - - /** - * Check if the given class represents a primitive (i.e. boolean, byte, - * char, short, int, long, float, or double) or a primitive wrapper - * (i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double). - * - * @param clazz the class to check - * - * @return whether the given class is a primitive or primitive wrapper class - */ - public static boolean isPrimitiveOrWrapper(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isPrimitive() || isPrimitiveWrapper(clazz)); - } - - /** - * Check if the given class represents a primitive wrapper, - * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double. - * - * @param clazz the class to check - * - * @return whether the given class is a primitive wrapper class - */ - public static boolean isPrimitiveWrapper(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return primitiveWrapperTypeMap.containsKey(clazz); - } - - /** - * Check if the given class represents an array of primitives, - * i.e. boolean, byte, char, short, int, long, float, or double. - * - * @param clazz the class to check - * - * @return whether the given class is a primitive array class - */ - public static boolean isPrimitiveArray(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isArray() && clazz.getComponentType().isPrimitive()); - } - - /** - * Check if the given class represents an array of primitive wrappers, - * i.e. Boolean, Byte, Character, Short, Integer, Long, Float, or Double. - * - * @param clazz the class to check - * - * @return whether the given class is a primitive wrapper array class - */ - public static boolean isPrimitiveWrapperArray(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isArray() && isPrimitiveWrapper(clazz.getComponentType())); - } - - /** - * Resolve the given class if it is a primitive class, - * returning the corresponding primitive wrapper type instead. - * - * @param clazz the class to check - * - * @return the original class, or a primitive wrapper for the original primitive type - */ - public static Class resolvePrimitiveIfNecessary(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return (clazz.isPrimitive() && clazz != void.class ? primitiveTypeToWrapperMap.get(clazz) : clazz); - } - - /** - * Determine if the given type is assignable from the given value, - * assuming setting by reflection. Considers primitive wrapper classes - * as assignable to the corresponding primitive types. - * - * @param type the target type - * @param value the value that should be assigned to the type - * - * @return if the type is assignable from the value - */ - public static boolean isAssignableValue(Class type, Object value) { - Assert.notNull(type, "Type must not be null"); - return (value != null ? isAssignable(type, value.getClass()) : !type.isPrimitive()); - } - - /** - * Check if the right-hand side type may be assigned to the left-hand side - * type, assuming setting by reflection. Considers primitive wrapper - * classes as assignable to the corresponding primitive types. - * - * @param lhsType the target type - * @param rhsType the value type that should be assigned to the target type - * - * @return if the target type is assignable from the value type - * @see TypeUtils#isAssignable - */ - public static boolean isAssignable(Class lhsType, Class rhsType) { - Assert.notNull(lhsType, "Left-hand side type must not be null"); - Assert.notNull(rhsType, "Right-hand side type must not be null"); - if (lhsType.isAssignableFrom(rhsType)) { - return true; - } - if (lhsType.isPrimitive()) { - Class resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType); - if (lhsType == resolvedPrimitive) { - return true; - } - } else { - Class resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); - if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) { - return true; - } - } - return false; - } - - /** - * Convert a "/"-based resource path to a "."-based fully qualified class name. - * - * @param resourcePath the resource path pointing to a class - * - * @return the corresponding fully qualified class name - */ - public static String convertResourcePathToClassName(String resourcePath) { - Assert.notNull(resourcePath, "Resource path must not be null"); - return resourcePath.replace(PATH_SEPARATOR, PACKAGE_SEPARATOR); - } - - /** - * Convert a "."-based fully qualified class name to a "/"-based resource path. - * - * @param className the fully qualified class name - * - * @return the corresponding resource path, pointing to the class - */ - public static String convertClassNameToResourcePath(String className) { - Assert.notNull(className, "Class name must not be null"); - return className.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR); - } - - /** - * Return a path suitable for use with {@code ClassLoader.getResource} - * (also suitable for use with {@code Class.getResource} by prepending a - * slash ('/') to the return value). Built by taking the package of the specified - * class file, converting all dots ('.') to slashes ('/'), adding a trailing slash - * if necessary, and concatenating the specified resource name to this. - *
As such, this function may be used to build a path suitable for - * loading a resource file that is in the same package as a class file, - * although {@link org.springframework.core.io.ClassPathResource} is usually - * even more convenient. - * - * @param clazz the Class whose package will be used as the base - * @param resourceName the resource name to append. A leading slash is optional. - * - * @return the built-up resource path - * @see ClassLoader#getResource - * @see Class#getResource - */ - public static String addResourcePathToPackagePath(Class clazz, String resourceName) { - Assert.notNull(resourceName, "Resource name must not be null"); - if (!resourceName.startsWith("/")) { - return classPackageAsResourcePath(clazz) + '/' + resourceName; - } - return classPackageAsResourcePath(clazz) + resourceName; - } - - /** - * Given an input class object, return a string which consists of the - * class's package name as a pathname, i.e., all dots ('.') are replaced by - * slashes ('/'). Neither a leading nor trailing slash is added. The result - * could be concatenated with a slash and the name of a resource and fed - * directly to {@code ClassLoader.getResource()}. For it to be fed to - * {@code Class.getResource} instead, a leading slash would also have - * to be prepended to the returned value. - * - * @param clazz the input class. A {@code null} value or the default - * (empty) package will result in an empty string ("") being returned. - * - * @return a path which represents the package name - * @see ClassLoader#getResource - * @see Class#getResource - */ - public static String classPackageAsResourcePath(Class clazz) { - if (clazz == null) { - return ""; - } - String className = clazz.getName(); - int packageEndIndex = className.lastIndexOf(PACKAGE_SEPARATOR); - if (packageEndIndex == -1) { - return ""; - } - String packageName = className.substring(0, packageEndIndex); - return packageName.replace(PACKAGE_SEPARATOR, PATH_SEPARATOR); - } - - /** - * Build a String that consists of the names of the classes/interfaces - * in the given array. - *

Basically like {@code AbstractCollection.toString()}, but stripping - * the "class "/"interface " prefix before every class name. - * - * @param classes an array of Class objects - * - * @return a String of form "[com.foo.Bar, com.foo.Baz]" - * @see java.util.AbstractCollection#toString() - */ - public static String classNamesToString(Class... classes) { - return classNamesToString(Arrays.asList(classes)); - } - - /** - * Build a String that consists of the names of the classes/interfaces - * in the given collection. - *

Basically like {@code AbstractCollection.toString()}, but stripping - * the "class "/"interface " prefix before every class name. - * - * @param classes a Collection of Class objects (may be {@code null}) - * - * @return a String of form "[com.foo.Bar, com.foo.Baz]" - * @see java.util.AbstractCollection#toString() - */ - public static String classNamesToString(Collection> classes) { - if (CollectionUtils.isEmpty(classes)) { - return "[]"; - } - StringBuilder sb = new StringBuilder("["); - for (Iterator> it = classes.iterator(); it.hasNext(); ) { - Class clazz = it.next(); - sb.append(clazz.getName()); - if (it.hasNext()) { - sb.append(", "); - } - } - sb.append("]"); - return sb.toString(); - } - - /** - * Return all interfaces that the given instance implements as an array, - * including ones implemented by superclasses. - * - * @param instance the instance to analyze for interfaces - * - * @return all interfaces that the given instance implements as an array - */ - public static Class[] getAllInterfaces(Object instance) { - Assert.notNull(instance, "Instance must not be null"); - return getAllInterfacesForClass(instance.getClass()); - } - - /** - * Return all interfaces that the given class implements as an array, - * including ones implemented by superclasses. - *

If the class itself is an interface, it gets returned as sole interface. - * - * @param clazz the class to analyze for interfaces - * - * @return all interfaces that the given object implements as an array - */ - public static Class[] getAllInterfacesForClass(Class clazz) { - return getAllInterfacesForClass(clazz, null); - } - - /** - * Return all interfaces that the given class implements as an array, - * including ones implemented by superclasses. - *

If the class itself is an interface, it gets returned as sole interface. - * - * @param clazz the class to analyze for interfaces - * @param classLoader the ClassLoader that the interfaces need to be visible in - * (may be {@code null} when accepting all declared interfaces) - * - * @return all interfaces that the given object implements as an array - */ - public static Class[] getAllInterfacesForClass(Class clazz, ClassLoader classLoader) { - return toClassArray(getAllInterfacesForClassAsSet(clazz, classLoader)); - } - - /** - * Copy the given {@code Collection} into a {@code Class} array. - *

The {@code Collection} must contain {@code Class} elements only. - * - * @param collection the {@code Collection} to copy - * - * @return the {@code Class} array - * @see StringUtils#toStringArray - * @since 3.1 - */ - public static Class[] toClassArray(Collection> collection) { - return collection.toArray(new Class[0]); - } - - /** - * Return all interfaces that the given class implements as a Set, - * including ones implemented by superclasses. - *

If the class itself is an interface, it gets returned as sole interface. - * - * @param clazz the class to analyze for interfaces - * @param classLoader the ClassLoader that the interfaces need to be visible in - * (may be {@code null} when accepting all declared interfaces) - * - * @return all interfaces that the given object implements as a Set - */ - public static Set> getAllInterfacesForClassAsSet(Class clazz, ClassLoader classLoader) { - Assert.notNull(clazz, "Class must not be null"); - if (clazz.isInterface() && isVisible(clazz, classLoader)) { - return Collections.singleton(clazz); - } - Set> interfaces = new LinkedHashSet<>(); - Class current = clazz; - while (current != null) { - Class[] ifcs = current.getInterfaces(); - for (Class ifc : ifcs) { - if (isVisible(ifc, classLoader)) { - interfaces.add(ifc); - } - } - current = current.getSuperclass(); - } - return interfaces; - } - - /** - * Check whether the given class is visible in the given ClassLoader. - * - * @param clazz the class to check (typically an interface) - * @param classLoader the ClassLoader to check against - * (may be {@code null} in which case this method will always return {@code true}) - */ - public static boolean isVisible(Class clazz, ClassLoader classLoader) { - if (classLoader == null) { - return true; - } - try { - if (clazz.getClassLoader() == classLoader) { - return true; - } - } catch (SecurityException ex) { - // Fall through to loadable check below - } - - // Visible if same Class can be loaded from given ClassLoader - return isLoadable(clazz, classLoader); - } - - /** - * Return all interfaces that the given instance implements as a Set, - * including ones implemented by superclasses. - * - * @param instance the instance to analyze for interfaces - * - * @return all interfaces that the given instance implements as a Set - */ - public static Set> getAllInterfacesAsSet(Object instance) { - Assert.notNull(instance, "Instance must not be null"); - return getAllInterfacesForClassAsSet(instance.getClass()); - } - - /** - * Return all interfaces that the given class implements as a Set, - * including ones implemented by superclasses. - *

If the class itself is an interface, it gets returned as sole interface. - * - * @param clazz the class to analyze for interfaces - * - * @return all interfaces that the given object implements as a Set - */ - public static Set> getAllInterfacesForClassAsSet(Class clazz) { - return getAllInterfacesForClassAsSet(clazz, null); - } - - /** - * Create a composite interface Class for the given interfaces, - * implementing the given interfaces in one single Class. - *

This implementation builds a JDK proxy class for the given interfaces. - * - * @param interfaces the interfaces to merge - * @param classLoader the ClassLoader to create the composite Class in - * - * @return the merged interface as Class - * @see Proxy#getProxyClass - */ - @SuppressWarnings("deprecation") - public static Class createCompositeInterface(Class[] interfaces, ClassLoader classLoader) { - Assert.notEmpty(interfaces, "Interfaces must not be empty"); - return Proxy.getProxyClass(classLoader, interfaces); - } - - /** - * Determine the common ancestor of the given classes, if any. - * - * @param clazz1 the class to introspect - * @param clazz2 the other class to introspect - * - * @return the common ancestor (i.e. common superclass, one interface - * extending the other), or {@code null} if none found. If any of the - * given classes is {@code null}, the other class will be returned. - * @since 3.2.6 - */ - public static Class determineCommonAncestor(Class clazz1, Class clazz2) { - if (clazz1 == null) { - return clazz2; - } - if (clazz2 == null) { - return clazz1; - } - if (clazz1.isAssignableFrom(clazz2)) { - return clazz1; - } - if (clazz2.isAssignableFrom(clazz1)) { - return clazz2; - } - Class ancestor = clazz1; - do { - ancestor = ancestor.getSuperclass(); - if (ancestor == null || Object.class == ancestor) { - return null; - } - } - while (!ancestor.isAssignableFrom(clazz2)); - return ancestor; - } - - /** - * Determine whether the given interface is a common Java language interface: - * {@link Serializable}, {@link Externalizable}, {@link Closeable}, {@link AutoCloseable}, - * {@link Cloneable}, {@link Comparable} - all of which can be ignored when looking - * for 'primary' user-level interfaces. Common characteristics: no service-level - * operations, no bean property methods, no default methods. - * - * @param ifc the interface to check - * - * @since 5.0.3 - */ - public static boolean isJavaLanguageInterface(Class ifc) { - return javaLanguageInterfaces.contains(ifc); - } - - /** - * Determine if the supplied class is an inner class, - * i.e. a non-static member of an enclosing class. - * - * @return {@code true} if the supplied class is an inner class - * @see Class#isMemberClass() - * @since 5.0.5 - */ - public static boolean isInnerClass(Class clazz) { - return (clazz.isMemberClass() && !Modifier.isStatic(clazz.getModifiers())); - } - - /** - * Check whether the given object is a CGLIB proxy. - * - * @param object the object to check - * - * @see #isCglibProxyClass(Class) - * @see org.springframework.aop.support.AopUtils#isCglibProxy(Object) - */ - public static boolean isCglibProxy(Object object) { - return isCglibProxyClass(object.getClass()); - } - - /** - * Check whether the specified class is a CGLIB-generated class. - * - * @param clazz the class to check - * - * @see #isCglibProxyClassName(String) - */ - public static boolean isCglibProxyClass(Class clazz) { - return (clazz != null && isCglibProxyClassName(clazz.getName())); - } - - /** - * Check whether the specified class name is a CGLIB-generated class. - * - * @param className the class name to check - */ - public static boolean isCglibProxyClassName(String className) { - return (className != null && className.contains(CGLIB_CLASS_SEPARATOR)); - } - - /** - * Return the user-defined class for the given instance: usually simply - * the class of the given instance, but the original class in case of a - * CGLIB-generated subclass. - * - * @param instance the instance to check - * - * @return the user-defined class - */ - public static Class getUserClass(Object instance) { - Assert.notNull(instance, "Instance must not be null"); - return getUserClass(instance.getClass()); - } - - /** - * Return the user-defined class for the given class: usually simply the given - * class, but the original class in case of a CGLIB-generated subclass. - * - * @param clazz the class to check - * - * @return the user-defined class - */ - public static Class getUserClass(Class clazz) { - if (clazz.getName().contains(CGLIB_CLASS_SEPARATOR)) { - Class superclass = clazz.getSuperclass(); - if (superclass != null && Object.class != superclass) { - return superclass; - } - } - return clazz; - } - - /** - * Return a descriptive name for the given object's type: usually simply - * the class name, but component type class name + "[]" for arrays, - * and an appended list of implemented interfaces for JDK proxies. - * - * @param value the value to introspect - * - * @return the qualified name of the class - */ - public static String getDescriptiveType(Object value) { - if (value == null) { - return null; - } - Class clazz = value.getClass(); - if (Proxy.isProxyClass(clazz)) { - StringBuilder result = new StringBuilder(clazz.getName()); - result.append(" implementing "); - Class[] ifcs = clazz.getInterfaces(); - for (int i = 0; i < ifcs.length; i++) { - result.append(ifcs[i].getName()); - if (i < ifcs.length - 1) { - result.append(','); - } - } - return result.toString(); - } else { - return clazz.getTypeName(); - } - } - - /** - * Check whether the given class matches the user-specified type name. - * - * @param clazz the class to check - * @param typeName the type name to match - */ - public static boolean matchesTypeName(Class clazz, String typeName) { - return (typeName != null && - (typeName.equals(clazz.getTypeName()) || typeName.equals(clazz.getSimpleName()))); - } - - /** - * Return the short string name of a Java class in uncapitalized JavaBeans - * property format. Strips the outer class name in case of an inner class. - * - * @param clazz the class - * - * @return the short name rendered in a standard JavaBeans property format - * @see Introspector#decapitalize(String) - */ - public static String getShortNameAsProperty(Class clazz) { - String shortName = getShortName(clazz); - int dotIndex = shortName.lastIndexOf(PACKAGE_SEPARATOR); - shortName = (dotIndex != -1 ? shortName.substring(dotIndex + 1) : shortName); - return Introspector.decapitalize(shortName); - } - - /** - * Get the class name without the qualified package name. - * - * @param clazz the class to get the short name for - * - * @return the class name of the class without the package name - */ - public static String getShortName(Class clazz) { - return getShortName(getQualifiedName(clazz)); - } - - /** - * Get the class name without the qualified package name. - * - * @param className the className to get the short name for - * - * @return the class name of the class without the package name - * @throws IllegalArgumentException if the className is empty - */ - public static String getShortName(String className) { - Assert.hasLength(className, "Class name must not be empty"); - int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR); - int nameEndIndex = className.indexOf(CGLIB_CLASS_SEPARATOR); - if (nameEndIndex == -1) { - nameEndIndex = className.length(); - } - String shortName = className.substring(lastDotIndex + 1, nameEndIndex); - shortName = shortName.replace(INNER_CLASS_SEPARATOR, PACKAGE_SEPARATOR); - return shortName; - } - - /** - * Return the qualified name of the given class: usually simply - * the class name, but component type class name + "[]" for arrays. - * - * @param clazz the class - * - * @return the qualified name of the class - */ - public static String getQualifiedName(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return clazz.getTypeName(); - } - - /** - * Determine the name of the class file, relative to the containing - * package: e.g. "String.class" - * - * @param clazz the class - * - * @return the file name of the ".class" file - */ - public static String getClassFileName(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - String className = clazz.getName(); - int lastDotIndex = className.lastIndexOf(PACKAGE_SEPARATOR); - return className.substring(lastDotIndex + 1) + CLASS_FILE_SUFFIX; - } - - /** - * Return the qualified name of the given method, consisting of - * fully qualified interface/class name + "." + method name. - * - * @param method the method - * - * @return the qualified name of the method - */ - public static String getQualifiedMethodName(Method method) { - return getQualifiedMethodName(method, null); - } - - /** - * Return the qualified name of the given method, consisting of - * fully qualified interface/class name + "." + method name. - * - * @param method the method - * @param clazz the clazz that the method is being invoked on - * (may be {@code null} to indicate the method's declaring class) - * - * @return the qualified name of the method - * @since 4.3.4 - */ - public static String getQualifiedMethodName(Method method, Class clazz) { - Assert.notNull(method, "Method must not be null"); - return (clazz != null ? clazz : method.getDeclaringClass()).getName() + '.' + method.getName(); - } - - /** - * Determine whether the given class has a public constructor with the given signature. - *

Essentially translates {@code NoSuchMethodException} to "false". - * - * @param clazz the clazz to analyze - * @param paramTypes the parameter types of the method - * - * @return whether the class has a corresponding constructor - * @see Class#getMethod - */ - public static boolean hasConstructor(Class clazz, Class... paramTypes) { - return (getConstructorIfAvailable(clazz, paramTypes) != null); - } - - /** - * Determine whether the given class has a public constructor with the given signature, - * and return it if available (else return {@code null}). - *

Essentially translates {@code NoSuchMethodException} to {@code null}. - * - * @param clazz the clazz to analyze - * @param paramTypes the parameter types of the method - * - * @return the constructor, or {@code null} if not found - * @see Class#getConstructor - */ - public static Constructor getConstructorIfAvailable(Class clazz, Class... paramTypes) { - Assert.notNull(clazz, "Class must not be null"); - try { - return clazz.getConstructor(paramTypes); - } catch (NoSuchMethodException ex) { - return null; - } - } - - /** - * Determine whether the given class has a public method with the given signature. - *

Essentially translates {@code NoSuchMethodException} to "false". - * - * @param clazz the clazz to analyze - * @param methodName the name of the method - * @param paramTypes the parameter types of the method - * - * @return whether the class has a corresponding method - * @see Class#getMethod - */ - public static boolean hasMethod(Class clazz, String methodName, Class... paramTypes) { - return (getMethodIfAvailable(clazz, methodName, paramTypes) != null); - } - - /** - * Determine whether the given class has a public method with the given signature, - * and return it if available (else return {@code null}). - *

In case of any signature specified, only returns the method if there is a - * unique candidate, i.e. a single public method with the specified name. - *

Essentially translates {@code NoSuchMethodException} to {@code null}. - * - * @param clazz the clazz to analyze - * @param methodName the name of the method - * @param paramTypes the parameter types of the method - * (may be {@code null} to indicate any signature) - * - * @return the method, or {@code null} if not found - * @see Class#getMethod - */ - public static Method getMethodIfAvailable(Class clazz, String methodName, Class... paramTypes) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(methodName, "Method name must not be null"); - if (paramTypes != null) { - try { - return clazz.getMethod(methodName, paramTypes); - } catch (NoSuchMethodException ex) { - return null; - } - } else { - Set candidates = new HashSet<>(1); - Method[] methods = clazz.getMethods(); - for (Method method : methods) { - if (methodName.equals(method.getName())) { - candidates.add(method); - } - } - if (candidates.size() == 1) { - return candidates.iterator().next(); - } - return null; - } - } - - /** - * Determine whether the given class has a public method with the given signature, - * and return it if available (else throws an {@code IllegalStateException}). - *

In case of any signature specified, only returns the method if there is a - * unique candidate, i.e. a single public method with the specified name. - *

Essentially translates {@code NoSuchMethodException} to {@code IllegalStateException}. - * - * @param clazz the clazz to analyze - * @param methodName the name of the method - * @param paramTypes the parameter types of the method - * (may be {@code null} to indicate any signature) - * - * @return the method (never {@code null}) - * @throws IllegalStateException if the method has not been found - * @see Class#getMethod - */ - public static Method getMethod(Class clazz, String methodName, Class... paramTypes) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(methodName, "Method name must not be null"); - if (paramTypes != null) { - try { - return clazz.getMethod(methodName, paramTypes); - } catch (NoSuchMethodException ex) { - throw new IllegalStateException("Expected method not found: " + ex); - } - } else { - Set candidates = new HashSet<>(1); - Method[] methods = clazz.getMethods(); - for (Method method : methods) { - if (methodName.equals(method.getName())) { - candidates.add(method); - } - } - if (candidates.size() == 1) { - return candidates.iterator().next(); - } else if (candidates.isEmpty()) { - throw new IllegalStateException("Expected method not found: " + clazz.getName() + '.' + methodName); - } else { - throw new IllegalStateException("No unique method found: " + clazz.getName() + '.' + methodName); - } - } - } - - /** - * Return the number of methods with a given name (with any argument types), - * for the given class and/or its superclasses. Includes non-public methods. - * - * @param clazz the clazz to check - * @param methodName the name of the method - * - * @return the number of methods with the given name - */ - public static int getMethodCountForName(Class clazz, String methodName) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(methodName, "Method name must not be null"); - int count = 0; - Method[] declaredMethods = clazz.getDeclaredMethods(); - for (Method method : declaredMethods) { - if (methodName.equals(method.getName())) { - count++; - } - } - Class[] ifcs = clazz.getInterfaces(); - for (Class ifc : ifcs) { - count += getMethodCountForName(ifc, methodName); - } - if (clazz.getSuperclass() != null) { - count += getMethodCountForName(clazz.getSuperclass(), methodName); - } - return count; - } - - /** - * Does the given class or one of its superclasses at least have one or more - * methods with the supplied name (with any argument types)? - * Includes non-public methods. - * - * @param clazz the clazz to check - * @param methodName the name of the method - * - * @return whether there is at least one method with the given name - */ - public static boolean hasAtLeastOneMethodWithName(Class clazz, String methodName) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(methodName, "Method name must not be null"); - Method[] declaredMethods = clazz.getDeclaredMethods(); - for (Method method : declaredMethods) { - if (method.getName().equals(methodName)) { - return true; - } - } - Class[] ifcs = clazz.getInterfaces(); - for (Class ifc : ifcs) { - if (hasAtLeastOneMethodWithName(ifc, methodName)) { - return true; - } - } - return (clazz.getSuperclass() != null && hasAtLeastOneMethodWithName(clazz.getSuperclass(), methodName)); - } - - /** - * Given a method, which may come from an interface, and a target class used - * in the current reflective invocation, find the corresponding target method - * if there is one. E.g. the method may be {@code IFoo.bar()} and the - * target class may be {@code DefaultFoo}. In this case, the method may be - * {@code DefaultFoo.bar()}. This enables attributes on that method to be found. - *

NOTE: In contrast to {@link org.springframework.aop.support.AopUtils#getMostSpecificMethod}, - * this method does not resolve Java 5 bridge methods automatically. - * Call {@link org.springframework.core.BridgeMethodResolver#findBridgedMethod} - * if bridge method resolution is desirable (e.g. for obtaining metadata from - * the original method definition). - *

NOTE: Since Spring 3.1.1, if Java security settings disallow reflective - * access (e.g. calls to {@code Class#getDeclaredMethods} etc, this implementation - * will fall back to returning the originally provided method. - * - * @param method the method to be invoked, which may come from an interface - * @param targetClass the target class for the current invocation. - * May be {@code null} or may not even implement the method. - * - * @return the specific target method, or the original method if the - * {@code targetClass} doesn't implement it or is {@code null} - */ - public static Method getMostSpecificMethod(Method method, Class targetClass) { - if (targetClass != null && targetClass != method.getDeclaringClass() && isOverridable(method, targetClass)) { - try { - if (Modifier.isPublic(method.getModifiers())) { - try { - return targetClass.getMethod(method.getName(), method.getParameterTypes()); - } catch (NoSuchMethodException ex) { - return method; - } - } else { - Method specificMethod = - ReflectionUtils.findMethod(targetClass, method.getName(), method.getParameterTypes()); - return (specificMethod != null ? specificMethod : method); - } - } catch (SecurityException ex) { - // Security settings are disallowing reflective access; fall back to 'method' below. - } - } - return method; - } - - /** - * Determine whether the given method is overridable in the given target class. - * - * @param method the method to check - * @param targetClass the target class to check against - */ - private static boolean isOverridable(Method method, Class targetClass) { - if (Modifier.isPrivate(method.getModifiers())) { - return false; - } - if (Modifier.isPublic(method.getModifiers()) || Modifier.isProtected(method.getModifiers())) { - return true; - } - return (targetClass == null || - getPackageName(method.getDeclaringClass()).equals(getPackageName(targetClass))); - } - - /** - * Determine the name of the package of the given class, - * e.g. "java.lang" for the {@code java.lang.String} class. - * - * @param clazz the class - * - * @return the package name, or the empty String if the class - * is defined in the default package - */ - public static String getPackageName(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - return getPackageName(clazz.getName()); - } - - /** - * Determine the name of the package of the given fully-qualified class name, - * e.g. "java.lang" for the {@code java.lang.String} class name. - * - * @param fqClassName the fully-qualified class name - * - * @return the package name, or the empty String if the class - * is defined in the default package - */ - public static String getPackageName(String fqClassName) { - Assert.notNull(fqClassName, "Class name must not be null"); - int lastDotIndex = fqClassName.lastIndexOf(PACKAGE_SEPARATOR); - return (lastDotIndex != -1 ? fqClassName.substring(0, lastDotIndex) : ""); - } - - /** - * Determine whether the given method is declared by the user or at least pointing to - * a user-declared method. - *

Checks {@link Method#isSynthetic()} (for implementation methods) as well as the - * {@code GroovyObject} interface (for interface methods; on an implementation class, - * implementations of the {@code GroovyObject} methods will be marked as synthetic anyway). - * Note that, despite being synthetic, bridge methods ({@link Method#isBridge()}) are considered - * as user-level methods since they are eventually pointing to a user-declared generic method. - * - * @param method the method to check - * - * @return {@code true} if the method can be considered as user-declared; [@code false} otherwise - */ - public static boolean isUserLevelMethod(Method method) { - Assert.notNull(method, "Method must not be null"); - return (method.isBridge() || (!method.isSynthetic() && !isGroovyObjectMethod(method))); - } - - private static boolean isGroovyObjectMethod(Method method) { - return method.getDeclaringClass().getName().equals("groovy.lang.GroovyObject"); - } - - /** - * Return a public static method of a class. - * - * @param clazz the class which defines the method - * @param methodName the static method name - * @param args the parameter types to the method - * - * @return the static method, or {@code null} if no static method was found - * @throws IllegalArgumentException if the method name is blank or the clazz is null - */ - public static Method getStaticMethod(Class clazz, String methodName, Class... args) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(methodName, "Method name must not be null"); - try { - Method method = clazz.getMethod(methodName, args); - return Modifier.isStatic(method.getModifiers()) ? method : null; - } catch (NoSuchMethodException ex) { - return null; - } - } - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/CollectionUtils.java b/src/main/java/com/antkorwin/xsync/springframework/util/CollectionUtils.java deleted file mode 100644 index a7e3eb6..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/CollectionUtils.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * Copyright 2002-2018 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.io.Serializable; -import java.util.*; - -/** - * Miscellaneous collection utility methods. - * Mainly for internal use within the framework. - * - * @author Juergen Hoeller - * @author Rob Harrop - * @author Arjen Poutsma - * @since 1.1.3 - */ -public abstract class CollectionUtils { - - /** - * Return {@code true} if the supplied Map is {@code null} or empty. - * Otherwise, return {@code false}. - * - * @param map the Map to check - * - * @return whether the given Map is empty - */ - public static boolean isEmpty(Map map) { - return (map == null || map.isEmpty()); - } - - /** - * Convert the supplied array into a List. A primitive array gets converted - * into a List of the appropriate wrapper type. - *

NOTE: Generally prefer the standard {@link Arrays#asList} method. - * This {@code arrayToList} method is just meant to deal with an incoming Object - * value that might be an {@code Object[]} or a primitive array at runtime. - *

A {@code null} source value will be converted to an empty List. - * - * @param source the (potentially primitive) array - * - * @return the converted List result - * @see ObjectUtils#toObjectArray(Object) - * @see Arrays#asList(Object[]) - */ - @SuppressWarnings("rawtypes") - public static List arrayToList(Object source) { - return Arrays.asList(ObjectUtils.toObjectArray(source)); - } - - /** - * Merge the given array into the given Collection. - * - * @param array the array to merge (may be {@code null}) - * @param collection the target Collection to merge the array into - */ - @SuppressWarnings("unchecked") - public static void mergeArrayIntoCollection(Object array, Collection collection) { - Object[] arr = ObjectUtils.toObjectArray(array); - for (Object elem : arr) { - collection.add((E) elem); - } - } - - /** - * Merge the given Properties instance into the given Map, - * copying all properties (key-value pairs) over. - *

Uses {@code Properties.propertyNames()} to even catch - * default properties linked into the original Properties instance. - * - * @param props the Properties instance to merge (may be {@code null}) - * @param map the target Map to merge the properties into - */ - @SuppressWarnings("unchecked") - public static void mergePropertiesIntoMap(Properties props, Map map) { - if (props != null) { - for (Enumeration en = props.propertyNames(); en.hasMoreElements(); ) { - String key = (String) en.nextElement(); - Object value = props.get(key); - if (value == null) { - // Allow for defaults fallback or potentially overridden accessor... - value = props.getProperty(key); - } - map.put((K) key, (V) value); - } - } - } - - /** - * Check whether the given Iterator contains the given element. - * - * @param iterator the Iterator to check - * @param element the element to look for - * - * @return {@code true} if found, {@code false} otherwise - */ - public static boolean contains(Iterator iterator, Object element) { - if (iterator != null) { - while (iterator.hasNext()) { - Object candidate = iterator.next(); - if (ObjectUtils.nullSafeEquals(candidate, element)) { - return true; - } - } - } - return false; - } - - /** - * Check whether the given Enumeration contains the given element. - * - * @param enumeration the Enumeration to check - * @param element the element to look for - * - * @return {@code true} if found, {@code false} otherwise - */ - public static boolean contains(Enumeration enumeration, Object element) { - if (enumeration != null) { - while (enumeration.hasMoreElements()) { - Object candidate = enumeration.nextElement(); - if (ObjectUtils.nullSafeEquals(candidate, element)) { - return true; - } - } - } - return false; - } - - /** - * Check whether the given Collection contains the given element instance. - *

Enforces the given instance to be present, rather than returning - * {@code true} for an equal element as well. - * - * @param collection the Collection to check - * @param element the element to look for - * - * @return {@code true} if found, {@code false} otherwise - */ - public static boolean containsInstance(Collection collection, Object element) { - if (collection != null) { - for (Object candidate : collection) { - if (candidate == element) { - return true; - } - } - } - return false; - } - - /** - * Return {@code true} if any element in '{@code candidates}' is - * contained in '{@code source}'; otherwise returns {@code false}. - * - * @param source the source Collection - * @param candidates the candidates to search for - * - * @return whether any of the candidates has been found - */ - public static boolean containsAny(Collection source, Collection candidates) { - if (isEmpty(source) || isEmpty(candidates)) { - return false; - } - for (Object candidate : candidates) { - if (source.contains(candidate)) { - return true; - } - } - return false; - } - - /** - * Return {@code true} if the supplied Collection is {@code null} or empty. - * Otherwise, return {@code false}. - * - * @param collection the Collection to check - * - * @return whether the given Collection is empty - */ - public static boolean isEmpty(Collection collection) { - return (collection == null || collection.isEmpty()); - } - - /** - * Return the first element in '{@code candidates}' that is contained in - * '{@code source}'. If no element in '{@code candidates}' is present in - * '{@code source}' returns {@code null}. Iteration order is - * {@link Collection} implementation specific. - * - * @param source the source Collection - * @param candidates the candidates to search for - * - * @return the first present object, or {@code null} if not found - */ - @SuppressWarnings("unchecked") - public static E findFirstMatch(Collection source, Collection candidates) { - if (isEmpty(source) || isEmpty(candidates)) { - return null; - } - for (Object candidate : candidates) { - if (source.contains(candidate)) { - return (E) candidate; - } - } - return null; - } - - /** - * Find a single value of one of the given types in the given Collection: - * searching the Collection for a value of the first type, then - * searching for a value of the second type, etc. - * - * @param collection the collection to search - * @param types the types to look for, in prioritized order - * - * @return a value of one of the given types found if there is a clear match, - * or {@code null} if none or more than one such value found - */ - public static Object findValueOfType(Collection collection, Class[] types) { - if (isEmpty(collection) || ObjectUtils.isEmpty(types)) { - return null; - } - for (Class type : types) { - Object value = findValueOfType(collection, type); - if (value != null) { - return value; - } - } - return null; - } - - /** - * Find a single value of the given type in the given Collection. - * - * @param collection the Collection to search - * @param type the type to look for - * - * @return a value of the given type found if there is a clear match, - * or {@code null} if none or more than one such value found - */ - @SuppressWarnings("unchecked") - public static T findValueOfType(Collection collection, Class type) { - if (isEmpty(collection)) { - return null; - } - T value = null; - for (Object element : collection) { - if (type == null || type.isInstance(element)) { - if (value != null) { - // More than one value found... no clear single value. - return null; - } - value = (T) element; - } - } - return value; - } - - /** - * Determine whether the given Collection only contains a single unique object. - * - * @param collection the Collection to check - * - * @return {@code true} if the collection contains a single reference or - * multiple references to the same instance, {@code false} otherwise - */ - public static boolean hasUniqueObject(Collection collection) { - if (isEmpty(collection)) { - return false; - } - boolean hasCandidate = false; - Object candidate = null; - for (Object elem : collection) { - if (!hasCandidate) { - hasCandidate = true; - candidate = elem; - } else if (candidate != elem) { - return false; - } - } - return true; - } - - /** - * Find the common element type of the given Collection, if any. - * - * @param collection the Collection to check - * - * @return the common element type, or {@code null} if no clear - * common type has been found (or the collection was empty) - */ - public static Class findCommonElementType(Collection collection) { - if (isEmpty(collection)) { - return null; - } - Class candidate = null; - for (Object val : collection) { - if (val != null) { - if (candidate == null) { - candidate = val.getClass(); - } else if (candidate != val.getClass()) { - return null; - } - } - } - return candidate; - } - - /** - * Retrieve the last element of the given Set, using {@link SortedSet#last()} - * or otherwise iterating over all elements (assuming a linked set). - * - * @param set the Set to check (may be {@code null} or empty) - * - * @return the last element, or {@code null} if none - * @see SortedSet - * @see LinkedHashMap#keySet() - * @see java.util.LinkedHashSet - * @since 5.0.3 - */ - public static T lastElement(Set set) { - if (isEmpty(set)) { - return null; - } - if (set instanceof SortedSet) { - return ((SortedSet) set).last(); - } - - // Full iteration necessary... - Iterator it = set.iterator(); - T last = null; - while (it.hasNext()) { - last = it.next(); - } - return last; - } - - /** - * Retrieve the last element of the given List, accessing the highest index. - * - * @param list the List to check (may be {@code null} or empty) - * - * @return the last element, or {@code null} if none - * @since 5.0.3 - */ - public static T lastElement(List list) { - if (isEmpty(list)) { - return null; - } - return list.get(list.size() - 1); - } - - /** - * Marshal the elements from the given enumeration into an array of the given type. - * Enumeration elements must be assignable to the type of the given array. The array - * returned will be a different instance than the array given. - */ - public static A[] toArray(Enumeration enumeration, A[] array) { - ArrayList elements = new ArrayList<>(); - while (enumeration.hasMoreElements()) { - elements.add(enumeration.nextElement()); - } - return elements.toArray(array); - } - - /** - * Adapt an enumeration to an iterator. - * - * @param enumeration the enumeration - * - * @return the iterator - */ - public static Iterator toIterator(Enumeration enumeration) { - return new EnumerationIterator<>(enumeration); - } - - /** - * Return an unmodifiable view of the specified multi-value map. - * - * @param map the map for which an unmodifiable view is to be returned. - * - * @return an unmodifiable view of the specified multi-value map. - * @since 3.1 - */ - @SuppressWarnings("unchecked") - public static MultiValueMap unmodifiableMultiValueMap(MultiValueMap map) { - Assert.notNull(map, "'map' must not be null"); - Map> result = new LinkedHashMap<>(map.size()); - map.forEach((key, value) -> { - List values = Collections.unmodifiableList(value); - result.put(key, (List) values); - }); - Map> unmodifiableMap = Collections.unmodifiableMap(result); - return toMultiValueMap(unmodifiableMap); - } - - /** - * Adapt a {@code Map>} to an {@code MultiValueMap}. - * - * @param map the original map - * - * @return the multi-value map - * @since 3.1 - */ - public static MultiValueMap toMultiValueMap(Map> map) { - return new MultiValueMapAdapter<>(map); - } - - /** - * Iterator wrapping an Enumeration. - */ - private static class EnumerationIterator implements Iterator { - - private final Enumeration enumeration; - - public EnumerationIterator(Enumeration enumeration) { - this.enumeration = enumeration; - } - - @Override - public boolean hasNext() { - return this.enumeration.hasMoreElements(); - } - - @Override - public E next() { - return this.enumeration.nextElement(); - } - - @Override - public void remove() throws UnsupportedOperationException { - throw new UnsupportedOperationException("Not supported"); - } - } - - - /** - * Adapts a Map to the MultiValueMap contract. - */ - @SuppressWarnings("serial") - private static class MultiValueMapAdapter implements MultiValueMap, Serializable { - - private final Map> map; - - public MultiValueMapAdapter(Map> map) { - Assert.notNull(map, "'map' must not be null"); - this.map = map; - } - - @Override - public V getFirst(K key) { - List values = this.map.get(key); - return (values != null ? values.get(0) : null); - } - - @Override - public void add(K key, V value) { - List values = this.map.computeIfAbsent(key, k -> new LinkedList<>()); - values.add(value); - } - - @Override - public void addAll(K key, List values) { - List currentValues = this.map.computeIfAbsent(key, k -> new LinkedList<>()); - currentValues.addAll(values); - } - - @Override - public void addAll(MultiValueMap values) { - for (Entry> entry : values.entrySet()) { - addAll(entry.getKey(), entry.getValue()); - } - } - - @Override - public void set(K key, V value) { - List values = new LinkedList<>(); - values.add(value); - this.map.put(key, values); - } - - @Override - public void setAll(Map values) { - values.forEach(this::set); - } - - @Override - public Map toSingleValueMap() { - LinkedHashMap singleValueMap = new LinkedHashMap<>(this.map.size()); - this.map.forEach((key, value) -> singleValueMap.put(key, value.get(0))); - return singleValueMap; - } - - @Override - public int size() { - return this.map.size(); - } - - @Override - public boolean isEmpty() { - return this.map.isEmpty(); - } - - @Override - public boolean containsKey(Object key) { - return this.map.containsKey(key); - } - - @Override - public boolean containsValue(Object value) { - return this.map.containsValue(value); - } - - @Override - public List get(Object key) { - return this.map.get(key); - } - - @Override - public List put(K key, List value) { - return this.map.put(key, value); - } - - @Override - public List remove(Object key) { - return this.map.remove(key); - } - - @Override - public void putAll(Map> map) { - this.map.putAll(map); - } - - @Override - public void clear() { - this.map.clear(); - } - - @Override - public Set keySet() { - return this.map.keySet(); - } - - @Override - public Collection> values() { - return this.map.values(); - } - - @Override - public Set>> entrySet() { - return this.map.entrySet(); - } - - @Override - public int hashCode() { - return this.map.hashCode(); - } - - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - return map.equals(other); - } - - @Override - public String toString() { - return this.map.toString(); - } - } - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/ConcurrentReferenceHashMap.java b/src/main/java/com/antkorwin/xsync/springframework/util/ConcurrentReferenceHashMap.java deleted file mode 100644 index 47f9fb0..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/ConcurrentReferenceHashMap.java +++ /dev/null @@ -1,1059 +0,0 @@ -/* - * Copyright 2002-2018 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.lang.ref.ReferenceQueue; -import java.lang.ref.SoftReference; -import java.lang.ref.WeakReference; -import java.lang.reflect.Array; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A {@link ConcurrentHashMap} that uses {@link ReferenceType#SOFT soft} or - * {@linkplain ReferenceType#WEAK weak} references for both {@code keys} and {@code values}. - *

- *

This class can be used as an alternative to - * {@code Collections.synchronizedMap(new WeakHashMap>())} in order to - * support better performance when accessed concurrently. This implementation follows the - * same design constraints as {@link ConcurrentHashMap} with the exception that - * {@code null} values and {@code null} keys are supported. - *

- *

NOTE: The use of references means that there is no guarantee that items - * placed into the map will be subsequently available. The garbage collector may discard - * references at any time, so it may appear that an unknown thread is silently removing - * entries. - *

- *

If not explicitly specified, this implementation will use - * {@linkplain SoftReference soft entry references}. - * - * @param the key type - * @param the value type - * - * @author Phillip Webb - * @author Juergen Hoeller - * @since 3.2 - */ -public class ConcurrentReferenceHashMap extends AbstractMap implements ConcurrentMap { - - private static final int DEFAULT_INITIAL_CAPACITY = 16; - - private static final float DEFAULT_LOAD_FACTOR = 0.75f; - - private static final int DEFAULT_CONCURRENCY_LEVEL = 16; - - private static final ReferenceType DEFAULT_REFERENCE_TYPE = ReferenceType.SOFT; - - private static final int MAXIMUM_CONCURRENCY_LEVEL = 1 << 16; - - private static final int MAXIMUM_SEGMENT_SIZE = 1 << 30; - - - /** - * Array of segments indexed using the high order bits from the hash. - */ - private final Segment[] segments; - - /** - * When the average number of references per table exceeds this value resize will be attempted. - */ - private final float loadFactor; - - /** - * The reference type: SOFT or WEAK. - */ - private final ReferenceType referenceType; - - /** - * The shift value used to calculate the size of the segments array and an index from the hash. - */ - private final int shift; - - /** - * Late binding entry set. - */ - private Set> entrySet; - - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - */ - public ConcurrentReferenceHashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per - * table exceeds this value, resize will be attempted. - * @param concurrencyLevel the expected number of threads that will concurrently - * write to the map - * @param referenceType the reference type used for entries (soft or weak) - */ - @SuppressWarnings("unchecked") - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, - ReferenceType referenceType) { - - Assert.isTrue(initialCapacity >= 0, "Initial capacity must not be negative"); - Assert.isTrue(loadFactor > 0f, "Load factor must be positive"); - Assert.isTrue(concurrencyLevel > 0, "Concurrency level must be positive"); - Assert.notNull(referenceType, "Reference type must not be null"); - this.loadFactor = loadFactor; - this.shift = calculateShift(concurrencyLevel, MAXIMUM_CONCURRENCY_LEVEL); - int size = 1 << this.shift; - this.referenceType = referenceType; - int roundedUpSegmentCapacity = (int) ((initialCapacity + size - 1L) / size); - this.segments = (Segment[]) Array.newInstance(Segment.class, size); - for (int i = 0; i < this.segments.length; i++) { - this.segments[i] = new Segment(roundedUpSegmentCapacity); - } - } - - /** - * Calculate a shift value that can be used to create a power-of-two value between - * the specified maximum and minimum values. - * - * @param minimumValue the minimum value - * @param maximumValue the maximum value - * - * @return the calculated shift (use {@code 1 << shift} to obtain a value) - */ - protected static int calculateShift(int minimumValue, int maximumValue) { - int shift = 0; - int value = 1; - while (value < minimumValue && value < maximumValue) { - value <<= 1; - shift++; - } - return shift; - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - */ - public ConcurrentReferenceHashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per table - * exceeds this value resize will be attempted - */ - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param concurrencyLevel the expected number of threads that will concurrently - * write to the map - */ - public ConcurrentReferenceHashMap(int initialCapacity, int concurrencyLevel) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, concurrencyLevel, DEFAULT_REFERENCE_TYPE); - } - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param referenceType the reference type used for entries (soft or weak) - */ - public ConcurrentReferenceHashMap(int initialCapacity, ReferenceType referenceType) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL, referenceType); - } - - - /** - * Create a new {@code ConcurrentReferenceHashMap} instance. - * - * @param initialCapacity the initial capacity of the map - * @param loadFactor the load factor. When the average number of references per - * table exceeds this value, resize will be attempted. - * @param concurrencyLevel the expected number of threads that will concurrently - * write to the map - */ - public ConcurrentReferenceHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { - this(initialCapacity, loadFactor, concurrencyLevel, DEFAULT_REFERENCE_TYPE); - } - - @Override - public V getOrDefault(Object key, V defaultValue) { - Entry entry = getEntryIfAvailable(key); - return (entry != null ? entry.getValue() : defaultValue); - } - - @Override - public V putIfAbsent(K key, V value) { - return put(key, value, false); - } - - @Override - public boolean remove(Object key, final Object value) { - Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { - @Override - protected Boolean execute(Reference reference, Entry entry) { - if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), value)) { - if (reference != null) { - reference.release(); - } - return true; - } - return false; - } - }); - return (result == Boolean.TRUE); - } - - @Override - public boolean replace(K key, final V oldValue, final V newValue) { - Boolean result = doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { - @Override - protected Boolean execute(Reference reference, Entry entry) { - if (entry != null && ObjectUtils.nullSafeEquals(entry.getValue(), oldValue)) { - entry.setValue(newValue); - return true; - } - return false; - } - }); - return (result == Boolean.TRUE); - } - - @Override - public V replace(K key, final V value) { - return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.SKIP_IF_EMPTY) { - @Override - protected V execute(Reference reference, Entry entry) { - if (entry != null) { - V oldValue = entry.getValue(); - entry.setValue(value); - return oldValue; - } - return null; - } - }); - } - - private V put(final K key, final V value, final boolean overwriteExisting) { - return doTask(key, new Task(TaskOption.RESTRUCTURE_BEFORE, TaskOption.RESIZE) { - @Override - - protected V execute(Reference reference, Entry entry, - Entries entries) { - if (entry != null) { - V oldValue = entry.getValue(); - if (overwriteExisting) { - entry.setValue(value); - } - return oldValue; - } - Assert.state(entries != null, "No entries segment"); - entries.add(value); - return null; - } - }); - } - - private T doTask(Object key, Task task) { - int hash = getHash(key); - return getSegmentForHash(hash).doTask(hash, key, task); - } - - private Entry getEntryIfAvailable(Object key) { - Reference reference = getReference(key, Restructure.WHEN_NECESSARY); - return (reference != null ? reference.get() : null); - } - - /** - * Return a {@link Reference} to the {@link Entry} for the specified {@code key}, - * or {@code null} if not found. - * - * @param key the key (can be {@code null}) - * @param restructure types of restructure allowed during this call - * - * @return the reference, or {@code null} if not found - */ - protected final Reference getReference(Object key, Restructure restructure) { - int hash = getHash(key); - return getSegmentForHash(hash).getReference(key, hash, restructure); - } - - /** - * Get the hash for a given object, apply an additional hash function to reduce - * collisions. This implementation uses the same Wang/Jenkins algorithm as - * {@link ConcurrentHashMap}. Subclasses can override to provide alternative hashing. - * - * @param o the object to hash (may be null) - * - * @return the resulting hash code - */ - protected int getHash(Object o) { - int hash = o == null ? 0 : o.hashCode(); - hash += (hash << 15) ^ 0xffffcd7d; - hash ^= (hash >>> 10); - hash += (hash << 3); - hash ^= (hash >>> 6); - hash += (hash << 2) + (hash << 14); - hash ^= (hash >>> 16); - return hash; - } - - private Segment getSegmentForHash(int hash) { - return this.segments[(hash >>> (32 - this.shift)) & (this.segments.length - 1)]; - } - - /** - * Remove any entries that have been garbage collected and are no longer referenced. - * Under normal circumstances garbage collected entries are automatically purged as - * items are added or removed from the Map. This method can be used to force a purge, - * and is useful when the Map is read frequently but updated less often. - */ - public void purgeUnreferencedEntries() { - for (Segment segment : this.segments) { - segment.restructureIfNecessary(false); - } - } - - @Override - public int size() { - int size = 0; - for (Segment segment : this.segments) { - size += segment.getCount(); - } - return size; - } - - @Override - public boolean containsKey(Object key) { - Entry entry = getEntryIfAvailable(key); - return (entry != null && ObjectUtils.nullSafeEquals(entry.getKey(), key)); - } - - @Override - public V get(Object key) { - Entry entry = getEntryIfAvailable(key); - return (entry != null ? entry.getValue() : null); - } - - @Override - public V put(K key, V value) { - return put(key, value, true); - } - - @Override - - public V remove(Object key) { - return doTask(key, new Task(TaskOption.RESTRUCTURE_AFTER, TaskOption.SKIP_IF_EMPTY) { - @Override - - protected V execute(Reference reference, Entry entry) { - if (entry != null) { - if (reference != null) { - reference.release(); - } - return entry.value; - } - return null; - } - }); - } - - @Override - public void clear() { - for (Segment segment : this.segments) { - segment.clear(); - } - } - - @Override - public Set> entrySet() { - if (this.entrySet == null) { - this.entrySet = new EntrySet(); - } - return this.entrySet; - } - - protected final float getLoadFactor() { - return this.loadFactor; - } - - protected final int getSegmentsSize() { - return this.segments.length; - } - - protected final Segment getSegment(int index) { - return this.segments[index]; - } - - /** - * Factory method that returns the {@link ReferenceManager}. - * This method will be called once for each {@link Segment}. - * - * @return a new reference manager - */ - protected ReferenceManager createReferenceManager() { - return new ReferenceManager(); - } - - - /** - * Various reference types supported by this map. - */ - public enum ReferenceType { - - /** - * Use {@link SoftReference}s - */ - SOFT, - - /** - * Use {@link WeakReference}s - */ - WEAK - } - - - /** - * Various options supported by a {@code Task}. - */ - private enum TaskOption { - - RESTRUCTURE_BEFORE, RESTRUCTURE_AFTER, SKIP_IF_EMPTY, RESIZE - } - - - /** - * The types of restructuring that can be performed. - */ - protected enum Restructure { - - WHEN_NECESSARY, NEVER - } - - - /** - * A reference to an {@link Entry} contained in the map. Implementations are usually - * wrappers around specific Java reference implementations (e.g., {@link SoftReference}). - */ - protected interface Reference { - - /** - * Return the referenced entry, or {@code null} if the entry is no longer available. - */ - Entry get(); - - /** - * Return the hash for the reference. - */ - int getHash(); - - /** - * Return the next reference in the chain, or {@code null} if none. - */ - Reference getNext(); - - /** - * Release this entry and ensure that it will be returned from - * {@code ReferenceManager#pollForPurge()}. - */ - void release(); - } - - /** - * A single map entry. - */ - protected static final class Entry implements Map.Entry { - - - private final K key; - - - private volatile V value; - - public Entry(K key, V value) { - this.key = key; - this.value = value; - } - - @Override - public final int hashCode() { - return (ObjectUtils.nullSafeHashCode(this.key) ^ ObjectUtils.nullSafeHashCode(this.value)); - } - - @Override - @SuppressWarnings("rawtypes") - public final boolean equals(Object other) { - if (this == other) { - return true; - } - if (!(other instanceof Map.Entry)) { - return false; - } - Map.Entry otherEntry = (Map.Entry) other; - return (ObjectUtils.nullSafeEquals(getKey(), otherEntry.getKey()) && - ObjectUtils.nullSafeEquals(getValue(), otherEntry.getValue())); - } - - @Override - public K getKey() { - return this.key; - } - - @Override - public V getValue() { - return this.value; - } - - @Override - public V setValue(V value) { - V previous = this.value; - this.value = value; - return previous; - } - - @Override - public String toString() { - return (this.key + "=" + this.value); - } - } - - /** - * Internal {@link Reference} implementation for {@link SoftReference}s. - */ - private static final class SoftEntryReference extends SoftReference> implements Reference { - - private final int hash; - - - private final Reference nextReference; - - public SoftEntryReference(Entry entry, int hash, Reference next, - ReferenceQueue> queue) { - - super(entry, queue); - this.hash = hash; - this.nextReference = next; - } - - @Override - public int getHash() { - return this.hash; - } - - @Override - public Reference getNext() { - return this.nextReference; - } - - @Override - public void release() { - enqueue(); - clear(); - } - } - - /** - * Internal {@link Reference} implementation for {@link WeakReference}s. - */ - private static final class WeakEntryReference extends WeakReference> implements Reference { - - private final int hash; - - - private final Reference nextReference; - - public WeakEntryReference(Entry entry, int hash, Reference next, - ReferenceQueue> queue) { - - super(entry, queue); - this.hash = hash; - this.nextReference = next; - } - - @Override - public int getHash() { - return this.hash; - } - - @Override - public Reference getNext() { - return this.nextReference; - } - - @Override - public void release() { - enqueue(); - clear(); - } - } - - /** - * A single segment used to divide the map to allow better concurrent performance. - */ - @SuppressWarnings("serial") - protected final class Segment extends ReentrantLock { - - private final ReferenceManager referenceManager; - - private final int initialSize; - - /** - * Array of references indexed using the low order bits from the hash. - * This property should only be set along with {@code resizeThreshold}. - */ - private volatile Reference[] references; - - /** - * The total number of references contained in this segment. This includes chained - * references and references that have been garbage collected but not purged. - */ - private volatile int count = 0; - - /** - * The threshold when resizing of the references should occur. When {@code count} - * exceeds this value references will be resized. - */ - private int resizeThreshold; - - public Segment(int initialCapacity) { - this.referenceManager = createReferenceManager(); - this.initialSize = 1 << calculateShift(initialCapacity, MAXIMUM_SEGMENT_SIZE); - this.references = createReferenceArray(this.initialSize); - this.resizeThreshold = (int) (this.references.length * getLoadFactor()); - } - - @SuppressWarnings("unchecked") - private Reference[] createReferenceArray(int size) { - return (Reference[]) Array.newInstance(Reference.class, size); - } - - public Reference getReference(Object key, int hash, Restructure restructure) { - if (restructure == Restructure.WHEN_NECESSARY) { - restructureIfNecessary(false); - } - if (this.count == 0) { - return null; - } - // Use a local copy to protect against other threads writing - Reference[] references = this.references; - int index = getIndex(hash, references); - Reference head = references[index]; - return findInChain(head, key, hash); - } - - /** - * Restructure the underlying data structure when it becomes necessary. This - * method can increase the size of the references table as well as purge any - * references that have been garbage collected. - * - * @param allowResize if resizing is permitted - */ - protected final void restructureIfNecessary(boolean allowResize) { - boolean needsResize = ((this.count > 0) && (this.count >= this.resizeThreshold)); - Reference reference = this.referenceManager.pollForPurge(); - if ((reference != null) || (needsResize && allowResize)) { - lock(); - try { - int countAfterRestructure = this.count; - - Set> toPurge = Collections.emptySet(); - if (reference != null) { - toPurge = new HashSet<>(); - while (reference != null) { - toPurge.add(reference); - reference = this.referenceManager.pollForPurge(); - } - } - countAfterRestructure -= toPurge.size(); - - // Recalculate taking into account count inside lock and items that - // will be purged - needsResize = (countAfterRestructure > 0 && countAfterRestructure >= this.resizeThreshold); - boolean resizing = false; - int restructureSize = this.references.length; - if (allowResize && needsResize && restructureSize < MAXIMUM_SEGMENT_SIZE) { - restructureSize <<= 1; - resizing = true; - } - - // Either create a new table or reuse the existing one - Reference[] restructured = (resizing ? createReferenceArray( - restructureSize) : this.references); - - // Restructure - for (int i = 0; i < this.references.length; i++) { - reference = this.references[i]; - if (!resizing) { - restructured[i] = null; - } - while (reference != null) { - if (!toPurge.contains(reference)) { - Entry entry = reference.get(); - if (entry != null) { - int index = getIndex(reference.getHash(), restructured); - restructured[index] = this.referenceManager.createReference( - entry, reference.getHash(), restructured[index]); - } - } - reference = reference.getNext(); - } - } - - // Replace volatile members - if (resizing) { - this.references = restructured; - this.resizeThreshold = (int) (this.references.length * getLoadFactor()); - } - this.count = Math.max(countAfterRestructure, 0); - } finally { - unlock(); - } - } - } - - private int getIndex(int hash, Reference[] references) { - return (hash & (references.length - 1)); - } - - private Reference findInChain(Reference reference, Object key, int hash) { - Reference currRef = reference; - while (currRef != null) { - if (currRef.getHash() == hash) { - Entry entry = currRef.get(); - if (entry != null) { - K entryKey = entry.getKey(); - if (ObjectUtils.nullSafeEquals(entryKey, key)) { - return currRef; - } - } - } - currRef = currRef.getNext(); - } - return null; - } - - /** - * Apply an update operation to this segment. - * The segment will be locked during the update. - * - * @param hash the hash of the key - * @param key the key - * @param task the update operation - * - * @return the result of the operation - */ - public T doTask(final int hash, final Object key, final Task task) { - boolean resize = task.hasOption(TaskOption.RESIZE); - if (task.hasOption(TaskOption.RESTRUCTURE_BEFORE)) { - restructureIfNecessary(resize); - } - if (task.hasOption(TaskOption.SKIP_IF_EMPTY) && this.count == 0) { - return task.execute(null, null, null); - } - lock(); - try { - final int index = getIndex(hash, this.references); - final Reference head = this.references[index]; - Reference reference = findInChain(head, key, hash); - Entry entry = (reference != null ? reference.get() : null); - Entries entries = new Entries() { - @Override - public void add(V value) { - @SuppressWarnings("unchecked") - Entry newEntry = new Entry<>((K) key, value); - Reference newReference = Segment.this.referenceManager - .createReference(newEntry, hash, head); - Segment.this.references[index] = newReference; - Segment.this.count++; - } - }; - return task.execute(reference, entry, entries); - } finally { - unlock(); - if (task.hasOption(TaskOption.RESTRUCTURE_AFTER)) { - restructureIfNecessary(resize); - } - } - } - - /** - * Clear all items from this segment. - */ - public void clear() { - if (this.count == 0) { - return; - } - lock(); - try { - this.references = createReferenceArray(this.initialSize); - this.resizeThreshold = (int) (this.references.length * getLoadFactor()); - this.count = 0; - } finally { - unlock(); - } - } - - /** - * Return the size of the current references array. - */ - public final int getSize() { - return this.references.length; - } - - /** - * Return the total number of references in this segment. - */ - public final int getCount() { - return this.count; - } - } - - /** - * A task that can be {@link Segment#doTask run} against a {@link Segment}. - */ - private abstract class Task { - - private final EnumSet options; - - public Task(TaskOption... options) { - this.options = (options.length == 0 ? EnumSet.noneOf(TaskOption.class) : EnumSet.of(options[0], options)); - } - - public boolean hasOption(TaskOption option) { - return this.options.contains(option); - } - - /** - * Execute the task. - * - * @param reference the found reference or {@code null} - * @param entry the found entry or {@code null} - * @param entries access to the underlying entries - * - * @return the result of the task - * @see #execute(Reference, Entry) - */ - protected T execute(Reference reference, Entry entry, - Entries entries) { - return execute(reference, entry); - } - - /** - * Convenience method that can be used for tasks that do not need access to {@link Entries}. - * - * @param reference the found reference or {@code null} - * @param entry the found entry or {@code null} - * - * @return the result of the task - * @see #execute(Reference, Entry, Entries) - */ - protected T execute(Reference reference, Entry entry) { - return null; - } - } - - /** - * Allows a task access to {@link Segment} entries. - */ - private abstract class Entries { - - /** - * Add a new entry with the specified value. - * - * @param value the value to add - */ - public abstract void add(V value); - } - - /** - * Internal entry-set implementation. - */ - private class EntrySet extends AbstractSet> { - - @Override - public Iterator> iterator() { - return new EntryIterator(); - } - - @Override - public int size() { - return ConcurrentReferenceHashMap.this.size(); - } - - @Override - public boolean contains(Object o) { - if (o != null && o instanceof Map.Entry) { - Map.Entry entry = (Map.Entry) o; - Reference reference = ConcurrentReferenceHashMap.this - .getReference(entry.getKey(), Restructure.NEVER); - Entry other = (reference != null ? reference.get() : null); - if (other != null) { - return ObjectUtils.nullSafeEquals(entry.getValue(), other.getValue()); - } - } - return false; - } - - @Override - public boolean remove(Object o) { - if (o instanceof Map.Entry) { - Map.Entry entry = (Map.Entry) o; - return ConcurrentReferenceHashMap.this.remove(entry.getKey(), entry.getValue()); - } - return false; - } - - @Override - public void clear() { - ConcurrentReferenceHashMap.this.clear(); - } - } - - /** - * Internal entry iterator implementation. - */ - private class EntryIterator implements Iterator> { - - private int segmentIndex; - - private int referenceIndex; - - - private Reference[] references; - - - private Reference reference; - - - private Entry next; - - - private Entry last; - - public EntryIterator() { - moveToNextSegment(); - } - - private void moveToNextSegment() { - this.reference = null; - this.references = null; - if (this.segmentIndex < ConcurrentReferenceHashMap.this.segments.length) { - this.references = ConcurrentReferenceHashMap.this.segments[this.segmentIndex].references; - this.segmentIndex++; - } - } - - @Override - public boolean hasNext() { - getNextIfNecessary(); - return (this.next != null); - } - - @Override - public Entry next() { - getNextIfNecessary(); - if (this.next == null) { - throw new NoSuchElementException(); - } - this.last = this.next; - this.next = null; - return this.last; - } - - @Override - public void remove() { - Assert.state(this.last != null, "No element to remove"); - ConcurrentReferenceHashMap.this.remove(this.last.getKey()); - } - - private void getNextIfNecessary() { - while (this.next == null) { - moveToNextReference(); - if (this.reference == null) { - return; - } - this.next = this.reference.get(); - } - } - - private void moveToNextReference() { - if (this.reference != null) { - this.reference = this.reference.getNext(); - } - while (this.reference == null && this.references != null) { - if (this.referenceIndex >= this.references.length) { - moveToNextSegment(); - this.referenceIndex = 0; - } else { - this.reference = this.references[this.referenceIndex]; - this.referenceIndex++; - } - } - } - } - - /** - * Strategy class used to manage {@link Reference}s. This class can be overridden if - * alternative reference types need to be supported. - */ - protected class ReferenceManager { - - private final ReferenceQueue> queue = new ReferenceQueue<>(); - - /** - * Factory method used to create a new {@link Reference}. - * - * @param entry the entry contained in the reference - * @param hash the hash - * @param next the next reference in the chain, or {@code null} if none - * - * @return a new {@link Reference} - */ - public Reference createReference(Entry entry, int hash, Reference next) { - if (ConcurrentReferenceHashMap.this.referenceType == ReferenceType.WEAK) { - return new WeakEntryReference<>(entry, hash, next, this.queue); - } - return new SoftEntryReference<>(entry, hash, next, this.queue); - } - - /** - * Return any reference that has been garbage collected and can be purged from the - * underlying structure or {@code null} if no references need purging. This - * method must be thread safe and ideally should not block when returning - * {@code null}. References should be returned once and only once. - * - * @return a reference to purge or {@code null} - */ - @SuppressWarnings("unchecked") - public Reference pollForPurge() { - return (Reference) this.queue.poll(); - } - } - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/MultiValueMap.java b/src/main/java/com/antkorwin/xsync/springframework/util/MultiValueMap.java deleted file mode 100644 index 5991acc..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/MultiValueMap.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2002-2017 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.util.List; -import java.util.Map; - -/** - * Extension of the {@code Map} interface that stores multiple values. - * - * @author Arjen Poutsma - * @since 3.0 - */ -public interface MultiValueMap extends Map> { - - /** - * Return the first value for the given key. - * - * @param key the key - * - * @return the first value for the specified key, or {@code null} if none - */ - V getFirst(K key); - - /** - * Add the given single value to the current list of values for the given key. - * - * @param key the key - * @param value the value to be added - */ - void add(K key, V value); - - /** - * Add all the values of the given list to the current list of values for the given key. - * - * @param key they key - * @param values the values to be added - * - * @since 5.0 - */ - void addAll(K key, List values); - - /** - * Add all the values of the given {@code MultiValueMap} to the current values. - * - * @param values the values to be added - * - * @since 5.0 - */ - void addAll(MultiValueMap values); - - /** - * Set the given single value under the given key. - * - * @param key the key - * @param value the value to set - */ - void set(K key, V value); - - /** - * Set the given values under. - * - * @param values the values. - */ - void setAll(Map values); - - /** - * Returns the first values contained in this {@code MultiValueMap}. - * - * @return a single value representation of this map - */ - Map toSingleValueMap(); - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/ObjectUtils.java b/src/main/java/com/antkorwin/xsync/springframework/util/ObjectUtils.java deleted file mode 100644 index 6f7c3da..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/ObjectUtils.java +++ /dev/null @@ -1,1026 +0,0 @@ -/* - * Copyright 2002-2017 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.lang.reflect.Array; -import java.util.Arrays; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; - -/** - * Miscellaneous object utility methods. - *

- *

Mainly for internal use within the framework. - *

- *

Thanks to Alex Ruiz for contributing several enhancements to this class! - * - * @author Juergen Hoeller - * @author Keith Donald - * @author Rod Johnson - * @author Rob Harrop - * @author Chris Beams - * @author Sam Brannen - * @see ClassUtils - * @see CollectionUtils - * @see StringUtils - * @since 19.03.2004 - */ -public abstract class ObjectUtils { - - private static final int INITIAL_HASH = 7; - private static final int MULTIPLIER = 31; - - private static final String EMPTY_STRING = ""; - private static final String NULL_STRING = "null"; - private static final String ARRAY_START = "{"; - private static final String ARRAY_END = "}"; - private static final String EMPTY_ARRAY = ARRAY_START + ARRAY_END; - private static final String ARRAY_ELEMENT_SEPARATOR = ", "; - - /** - * Check whether the given exception is compatible with the specified - * exception types, as declared in a throws clause. - * - * @param ex the exception to check - * @param declaredExceptions the exception types declared in the throws clause - * - * @return whether the given exception is compatible - */ - public static boolean isCompatibleWithThrowsClause(Throwable ex, Class... declaredExceptions) { - if (!isCheckedException(ex)) { - return true; - } - if (declaredExceptions != null) { - for (Class declaredException : declaredExceptions) { - if (declaredException.isInstance(ex)) { - return true; - } - } - } - return false; - } - - /** - * Return whether the given throwable is a checked exception: - * that is, neither a RuntimeException nor an Error. - * - * @param ex the throwable to check - * - * @return whether the throwable is a checked exception - * @see Exception - * @see RuntimeException - * @see Error - */ - public static boolean isCheckedException(Throwable ex) { - return !(ex instanceof RuntimeException || ex instanceof Error); - } - - /** - * Determine whether the given object is an array: - * either an Object array or a primitive array. - * - * @param obj the object to check - */ - public static boolean isArray(Object obj) { - return (obj != null && obj.getClass().isArray()); - } - - /** - * Determine whether the given array is empty: - * i.e. {@code null} or of zero length. - * - * @param array the array to check - * - * @see #isEmpty(Object) - */ - public static boolean isEmpty(Object[] array) { - return (array == null || array.length == 0); - } - - /** - * Determine whether the given object is empty. - *

This method supports the following object types. - *

- *

If the given object is non-null and not one of the aforementioned - * supported types, this method returns {@code false}. - * - * @param obj the object to check - * - * @return {@code true} if the object is {@code null} or empty - * @see Optional#isPresent() - * @see ObjectUtils#isEmpty(Object[]) - * @see StringUtils#hasLength(CharSequence) - * @see StringUtils#isEmpty(Object) - * @see CollectionUtils#isEmpty(Collection) - * @see CollectionUtils#isEmpty(Map) - * @since 4.2 - */ - @SuppressWarnings("rawtypes") - public static boolean isEmpty(Object obj) { - if (obj == null) { - return true; - } - - if (obj instanceof Optional) { - return !((Optional) obj).isPresent(); - } - if (obj instanceof CharSequence) { - return ((CharSequence) obj).length() == 0; - } - if (obj.getClass().isArray()) { - return Array.getLength(obj) == 0; - } - if (obj instanceof Collection) { - return ((Collection) obj).isEmpty(); - } - if (obj instanceof Map) { - return ((Map) obj).isEmpty(); - } - - // else - return false; - } - - /** - * Unwrap the given object which is potentially a {@link Optional}. - * - * @param obj the candidate object - * - * @return either the value held within the {@code Optional}, {@code null} - * if the {@code Optional} is empty, or simply the given object as-is - * @since 5.0 - */ - public static Object unwrapOptional(Object obj) { - if (obj instanceof Optional) { - Optional optional = (Optional) obj; - if (!optional.isPresent()) { - return null; - } - Object result = optional.get(); - Assert.isTrue(!(result instanceof Optional), "Multi-level Optional usage not supported"); - return result; - } - return obj; - } - - /** - * Check whether the given array contains the given element. - * - * @param array the array to check (may be {@code null}, - * in which case the return value will always be {@code false}) - * @param element the element to check for - * - * @return whether the element has been found in the given array - */ - public static boolean containsElement(Object[] array, Object element) { - if (array == null) { - return false; - } - for (Object arrayEle : array) { - if (nullSafeEquals(arrayEle, element)) { - return true; - } - } - return false; - } - - /** - * Determine if the given objects are equal, returning {@code true} if - * both are {@code null} or {@code false} if only one is {@code null}. - *

Compares arrays with {@code Arrays.equals}, performing an equality - * check based on the array elements rather than the array reference. - * - * @param o1 first Object to compare - * @param o2 second Object to compare - * - * @return whether the given objects are equal - * @see Object#equals(Object) - * @see Arrays#equals - */ - public static boolean nullSafeEquals(Object o1, Object o2) { - if (o1 == o2) { - return true; - } - if (o1 == null || o2 == null) { - return false; - } - if (o1.equals(o2)) { - return true; - } - if (o1.getClass().isArray() && o2.getClass().isArray()) { - return arrayEquals(o1, o2); - } - return false; - } - - /** - * Compare the given arrays with {@code Arrays.equals}, performing an equality - * check based on the array elements rather than the array reference. - * - * @param o1 first array to compare - * @param o2 second array to compare - * - * @return whether the given objects are equal - * @see #nullSafeEquals(Object, Object) - * @see Arrays#equals - */ - private static boolean arrayEquals(Object o1, Object o2) { - if (o1 instanceof Object[] && o2 instanceof Object[]) { - return Arrays.equals((Object[]) o1, (Object[]) o2); - } - if (o1 instanceof boolean[] && o2 instanceof boolean[]) { - return Arrays.equals((boolean[]) o1, (boolean[]) o2); - } - if (o1 instanceof byte[] && o2 instanceof byte[]) { - return Arrays.equals((byte[]) o1, (byte[]) o2); - } - if (o1 instanceof char[] && o2 instanceof char[]) { - return Arrays.equals((char[]) o1, (char[]) o2); - } - if (o1 instanceof double[] && o2 instanceof double[]) { - return Arrays.equals((double[]) o1, (double[]) o2); - } - if (o1 instanceof float[] && o2 instanceof float[]) { - return Arrays.equals((float[]) o1, (float[]) o2); - } - if (o1 instanceof int[] && o2 instanceof int[]) { - return Arrays.equals((int[]) o1, (int[]) o2); - } - if (o1 instanceof long[] && o2 instanceof long[]) { - return Arrays.equals((long[]) o1, (long[]) o2); - } - if (o1 instanceof short[] && o2 instanceof short[]) { - return Arrays.equals((short[]) o1, (short[]) o2); - } - return false; - } - - /** - * Check whether the given array of enum constants contains a constant with the given name, - * ignoring case when determining a match. - * - * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() - * @param constant the constant name to find (must not be null or empty string) - * - * @return whether the constant has been found in the given array - */ - public static boolean containsConstant(Enum[] enumValues, String constant) { - return containsConstant(enumValues, constant, false); - } - - /** - * Check whether the given array of enum constants contains a constant with the given name. - * - * @param enumValues the enum values to check, typically the product of a call to MyEnum.values() - * @param constant the constant name to find (must not be null or empty string) - * @param caseSensitive whether case is significant in determining a match - * - * @return whether the constant has been found in the given array - */ - public static boolean containsConstant(Enum[] enumValues, String constant, boolean caseSensitive) { - for (Enum candidate : enumValues) { - if (caseSensitive ? - candidate.toString().equals(constant) : - candidate.toString().equalsIgnoreCase(constant)) { - return true; - } - } - return false; - } - - /** - * Case insensitive alternative to {@link Enum#valueOf(Class, String)}. - * - * @param the concrete Enum type - * @param enumValues the array of all Enum constants in question, usually per Enum.values() - * @param constant the constant to get the enum value of - * - * @throws IllegalArgumentException if the given constant is not found in the given array - * of enum values. Use {@link #containsConstant(Enum[], String)} as a guard to avoid this exception. - */ - public static > E caseInsensitiveValueOf(E[] enumValues, String constant) { - for (E candidate : enumValues) { - if (candidate.toString().equalsIgnoreCase(constant)) { - return candidate; - } - } - throw new IllegalArgumentException( - String.format("constant [%s] does not exist in enum type %s", - constant, enumValues.getClass().getComponentType().getName())); - } - - - //--------------------------------------------------------------------- - // Convenience methods for content-based equality/hash-code handling - //--------------------------------------------------------------------- - - /** - * Append the given object to the given array, returning a new array - * consisting of the input array contents plus the given object. - * - * @param array the array to append to (can be {@code null}) - * @param obj the object to append - * - * @return the new array (of the same component type; never {@code null}) - */ - public static A[] addObjectToArray(A[] array, O obj) { - Class compType = Object.class; - if (array != null) { - compType = array.getClass().getComponentType(); - } else if (obj != null) { - compType = obj.getClass(); - } - int newArrLength = (array != null ? array.length + 1 : 1); - @SuppressWarnings("unchecked") - A[] newArr = (A[]) Array.newInstance(compType, newArrLength); - if (array != null) { - System.arraycopy(array, 0, newArr, 0, array.length); - } - newArr[newArr.length - 1] = obj; - return newArr; - } - - /** - * Convert the given array (which may be a primitive array) to an - * object array (if necessary of primitive wrapper objects). - *

A {@code null} source value will be converted to an - * empty Object array. - * - * @param source the (potentially primitive) array - * - * @return the corresponding object array (never {@code null}) - * @throws IllegalArgumentException if the parameter is not an array - */ - public static Object[] toObjectArray(Object source) { - if (source instanceof Object[]) { - return (Object[]) source; - } - if (source == null) { - return new Object[0]; - } - if (!source.getClass().isArray()) { - throw new IllegalArgumentException("Source is not an array: " + source); - } - int length = Array.getLength(source); - if (length == 0) { - return new Object[0]; - } - Class wrapperType = Array.get(source, 0).getClass(); - Object[] newArray = (Object[]) Array.newInstance(wrapperType, length); - for (int i = 0; i < length; i++) { - newArray[i] = Array.get(source, i); - } - return newArray; - } - - /** - * Return as hash code for the given object; typically the value of - * {@code Object#hashCode()}}. If the object is an array, - * this method will delegate to any of the {@code nullSafeHashCode} - * methods for arrays in this class. If the object is {@code null}, - * this method returns 0. - * - * @see Object#hashCode() - * @see #nullSafeHashCode(Object[]) - * @see #nullSafeHashCode(boolean[]) - * @see #nullSafeHashCode(byte[]) - * @see #nullSafeHashCode(char[]) - * @see #nullSafeHashCode(double[]) - * @see #nullSafeHashCode(float[]) - * @see #nullSafeHashCode(int[]) - * @see #nullSafeHashCode(long[]) - * @see #nullSafeHashCode(short[]) - */ - public static int nullSafeHashCode(Object obj) { - if (obj == null) { - return 0; - } - if (obj.getClass().isArray()) { - if (obj instanceof Object[]) { - return nullSafeHashCode((Object[]) obj); - } - if (obj instanceof boolean[]) { - return nullSafeHashCode((boolean[]) obj); - } - if (obj instanceof byte[]) { - return nullSafeHashCode((byte[]) obj); - } - if (obj instanceof char[]) { - return nullSafeHashCode((char[]) obj); - } - if (obj instanceof double[]) { - return nullSafeHashCode((double[]) obj); - } - if (obj instanceof float[]) { - return nullSafeHashCode((float[]) obj); - } - if (obj instanceof int[]) { - return nullSafeHashCode((int[]) obj); - } - if (obj instanceof long[]) { - return nullSafeHashCode((long[]) obj); - } - if (obj instanceof short[]) { - return nullSafeHashCode((short[]) obj); - } - } - return obj.hashCode(); - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(Object[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (Object element : array) { - hash = MULTIPLIER * hash + nullSafeHashCode(element); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(boolean[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (boolean element : array) { - hash = MULTIPLIER * hash + Boolean.hashCode(element); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(byte[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (byte element : array) { - hash = MULTIPLIER * hash + element; - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(char[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (char element : array) { - hash = MULTIPLIER * hash + element; - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(double[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (double element : array) { - hash = MULTIPLIER * hash + Double.hashCode(element); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(float[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (float element : array) { - hash = MULTIPLIER * hash + Float.hashCode(element); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(int[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (int element : array) { - hash = MULTIPLIER * hash + element; - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(long[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (long element : array) { - hash = MULTIPLIER * hash + Long.hashCode(element); - } - return hash; - } - - /** - * Return a hash code based on the contents of the specified array. - * If {@code array} is {@code null}, this method returns 0. - */ - public static int nullSafeHashCode(short[] array) { - if (array == null) { - return 0; - } - int hash = INITIAL_HASH; - for (short element : array) { - hash = MULTIPLIER * hash + element; - } - return hash; - } - - /** - * Return the same value as {@link Boolean#hashCode(boolean)}}. - * - * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant - */ - @Deprecated - public static int hashCode(boolean bool) { - return Boolean.hashCode(bool); - } - - /** - * Return the same value as {@link Double#hashCode(double)}}. - * - * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant - */ - @Deprecated - public static int hashCode(double dbl) { - return Double.hashCode(dbl); - } - - /** - * Return the same value as {@link Float#hashCode(float)}}. - * - * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant - */ - @Deprecated - public static int hashCode(float flt) { - return Float.hashCode(flt); - } - - /** - * Return the same value as {@link Long#hashCode(long)}}. - * - * @deprecated as of Spring Framework 5.0, in favor of the native JDK 8 variant - */ - @Deprecated - public static int hashCode(long lng) { - return Long.hashCode(lng); - } - - - //--------------------------------------------------------------------- - // Convenience methods for toString output - //--------------------------------------------------------------------- - - /** - * Return a String representation of an object's overall identity. - * - * @param obj the object (may be {@code null}) - * - * @return the object's identity as String representation, - * or an empty String if the object was {@code null} - */ - public static String identityToString(Object obj) { - if (obj == null) { - return EMPTY_STRING; - } - return obj.getClass().getName() + "@" + getIdentityHexString(obj); - } - - /** - * Return a hex String form of an object's identity hash code. - * - * @param obj the object - * - * @return the object's identity code in hex notation - */ - public static String getIdentityHexString(Object obj) { - return Integer.toHexString(System.identityHashCode(obj)); - } - - /** - * Return a content-based String representation if {@code obj} is - * not {@code null}; otherwise returns an empty String. - *

Differs from {@link #nullSafeToString(Object)} in that it returns - * an empty String rather than "null" for a {@code null} value. - * - * @param obj the object to build a display String for - * - * @return a display String representation of {@code obj} - * @see #nullSafeToString(Object) - */ - public static String getDisplayString(Object obj) { - if (obj == null) { - return EMPTY_STRING; - } - return nullSafeToString(obj); - } - - /** - * Return a String representation of the specified Object. - *

Builds a String representation of the contents in case of an array. - * Returns {@code "null"} if {@code obj} is {@code null}. - * - * @param obj the object to build a String representation for - * - * @return a String representation of {@code obj} - */ - public static String nullSafeToString(Object obj) { - if (obj == null) { - return NULL_STRING; - } - if (obj instanceof String) { - return (String) obj; - } - if (obj instanceof Object[]) { - return nullSafeToString((Object[]) obj); - } - if (obj instanceof boolean[]) { - return nullSafeToString((boolean[]) obj); - } - if (obj instanceof byte[]) { - return nullSafeToString((byte[]) obj); - } - if (obj instanceof char[]) { - return nullSafeToString((char[]) obj); - } - if (obj instanceof double[]) { - return nullSafeToString((double[]) obj); - } - if (obj instanceof float[]) { - return nullSafeToString((float[]) obj); - } - if (obj instanceof int[]) { - return nullSafeToString((int[]) obj); - } - if (obj instanceof long[]) { - return nullSafeToString((long[]) obj); - } - if (obj instanceof short[]) { - return nullSafeToString((short[]) obj); - } - String str = obj.toString(); - return (str != null ? str : EMPTY_STRING); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(Object[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(String.valueOf(array[i])); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(boolean[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(byte[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(char[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append("'").append(array[i]).append("'"); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(double[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(float[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(int[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(long[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Return a String representation of the contents of the specified array. - *

The String representation consists of a list of the array's elements, - * enclosed in curly braces ({@code "{}"}). Adjacent elements are separated - * by the characters {@code ", "} (a comma followed by a space). Returns - * {@code "null"} if {@code array} is {@code null}. - * - * @param array the array to build a String representation for - * - * @return a String representation of {@code array} - */ - public static String nullSafeToString(short[] array) { - if (array == null) { - return NULL_STRING; - } - int length = array.length; - if (length == 0) { - return EMPTY_ARRAY; - } - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < length; i++) { - if (i == 0) { - sb.append(ARRAY_START); - } else { - sb.append(ARRAY_ELEMENT_SEPARATOR); - } - sb.append(array[i]); - } - sb.append(ARRAY_END); - return sb.toString(); - } - - /** - * Determine the class name for the given object. - *

Returns {@code "null"} if {@code obj} is {@code null}. - * - * @param obj the object to introspect (may be {@code null}) - * - * @return the corresponding class name - */ - public static String nullSafeClassName(Object obj) { - return (obj != null ? obj.getClass().getName() : NULL_STRING); - } - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/ReflectionUtils.java b/src/main/java/com/antkorwin/xsync/springframework/util/ReflectionUtils.java deleted file mode 100644 index cc8b952..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/ReflectionUtils.java +++ /dev/null @@ -1,893 +0,0 @@ -/* - * Copyright 2002-2018 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 - * - * 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.antkorwin.xsync.springframework.util; - - -import java.lang.reflect.*; -import java.sql.SQLException; -import java.util.*; - -/** - * Simple utility class for working with the reflection API and handling - * reflection exceptions. - *

- *

Only intended for internal use. - * - * @author Juergen Hoeller - * @author Rob Harrop - * @author Rod Johnson - * @author Costin Leau - * @author Sam Brannen - * @author Chris Beams - * @since 1.2.2 - */ -public abstract class ReflectionUtils { - - /** - * Pre-built FieldFilter that matches all non-static, non-final fields. - */ - public static final FieldFilter COPYABLE_FIELDS = - field -> !(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())); - /** - * Pre-built MethodFilter that matches all non-bridge methods. - */ - public static final MethodFilter NON_BRIDGED_METHODS = - (method -> !method.isBridge()); - /** - * Pre-built MethodFilter that matches all non-bridge non-synthetic methods - * which are not declared on {@code java.lang.Object}. - */ - public static final MethodFilter USER_DECLARED_METHODS = - (method -> (!method.isBridge() && !method.isSynthetic() && method.getDeclaringClass() != Object.class)); - /** - * Naming prefix for CGLIB-renamed methods. - * - * @see #isCglibRenamedMethod - */ - private static final String CGLIB_RENAMED_METHOD_PREFIX = "CGLIB$"; - private static final Method[] NO_METHODS = {}; - private static final Field[] NO_FIELDS = {}; - /** - * Cache for {@link Class#getDeclaredMethods()} plus equivalent default methods - * from Java 8 based interfaces, allowing for fast iteration. - */ - private static final Map, Method[]> declaredMethodsCache = new ConcurrentReferenceHashMap<>(256); - /** - * Cache for {@link Class#getDeclaredFields()}, allowing for fast iteration. - */ - private static final Map, Field[]> declaredFieldsCache = new ConcurrentReferenceHashMap<>(256); - - /** - * Attempt to find a {@link Field field} on the supplied {@link Class} with the - * supplied {@code name}. Searches all superclasses up to {@link Object}. - * - * @param clazz the class to introspect - * @param name the name of the field - * - * @return the corresponding Field object, or {@code null} if not found - */ - public static Field findField(Class clazz, String name) { - return findField(clazz, name, null); - } - - /** - * Attempt to find a {@link Field field} on the supplied {@link Class} with the - * supplied {@code name} and/or {@link Class type}. Searches all superclasses - * up to {@link Object}. - * - * @param clazz the class to introspect - * @param name the name of the field (may be {@code null} if type is specified) - * @param type the type of the field (may be {@code null} if name is specified) - * - * @return the corresponding Field object, or {@code null} if not found - */ - public static Field findField(Class clazz, String name, Class type) { - Assert.notNull(clazz, "Class must not be null"); - Assert.isTrue(name != null || type != null, "Either name or type of the field must be specified"); - Class searchType = clazz; - while (Object.class != searchType && searchType != null) { - Field[] fields = getDeclaredFields(searchType); - for (Field field : fields) { - if ((name == null || name.equals(field.getName())) && - (type == null || type.equals(field.getType()))) { - return field; - } - } - searchType = searchType.getSuperclass(); - } - return null; - } - - /** - * This variant retrieves {@link Class#getDeclaredFields()} from a local cache - * in order to avoid the JVM's SecurityManager check and defensive array copying. - * - * @param clazz the class to introspect - * - * @return the cached array of fields - * @throws IllegalStateException if introspection fails - * @see Class#getDeclaredFields() - */ - private static Field[] getDeclaredFields(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - Field[] result = declaredFieldsCache.get(clazz); - if (result == null) { - try { - result = clazz.getDeclaredFields(); - declaredFieldsCache.put(clazz, (result.length == 0 ? NO_FIELDS : result)); - } catch (Throwable ex) { - throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + - "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); - } - } - return result; - } - - /** - * Set the field represented by the supplied {@link Field field object} on the - * specified {@link Object target object} to the specified {@code value}. - * In accordance with {@link Field#set(Object, Object)} semantics, the new value - * is automatically unwrapped if the underlying field has a primitive type. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. - * - * @param field the field to set - * @param target the target object on which to set the field - * @param value the value to set (may be {@code null}) - */ - public static void setField(Field field, Object target, Object value) { - try { - field.set(target, value); - } catch (IllegalAccessException ex) { - handleReflectionException(ex); - throw new IllegalStateException( - "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); - } - } - - /** - * Handle the given reflection exception. Should only be called if no - * checked exception is expected to be thrown by the target method. - *

Throws the underlying RuntimeException or Error in case of an - * InvocationTargetException with such a root cause. Throws an - * IllegalStateException with an appropriate message or - * UndeclaredThrowableException otherwise. - * - * @param ex the reflection exception to handle - */ - public static void handleReflectionException(Exception ex) { - if (ex instanceof NoSuchMethodException) { - throw new IllegalStateException("Method not found: " + ex.getMessage()); - } - if (ex instanceof IllegalAccessException) { - throw new IllegalStateException("Could not access method: " + ex.getMessage()); - } - if (ex instanceof InvocationTargetException) { - handleInvocationTargetException((InvocationTargetException) ex); - } - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - throw new UndeclaredThrowableException(ex); - } - - /** - * Handle the given invocation target exception. Should only be called if no - * checked exception is expected to be thrown by the target method. - *

Throws the underlying RuntimeException or Error in case of such a root - * cause. Throws an UndeclaredThrowableException otherwise. - * - * @param ex the invocation target exception to handle - */ - public static void handleInvocationTargetException(InvocationTargetException ex) { - rethrowRuntimeException(ex.getTargetException()); - } - - /** - * Rethrow the given {@link Throwable exception}, which is presumably the - * target exception of an {@link InvocationTargetException}. - * Should only be called if no checked exception is expected to be thrown - * by the target method. - *

Rethrows the underlying exception cast to a {@link RuntimeException} or - * {@link Error} if appropriate; otherwise, throws an - * {@link UndeclaredThrowableException}. - * - * @param ex the exception to rethrow - * - * @throws RuntimeException the rethrown exception - */ - public static void rethrowRuntimeException(Throwable ex) { - if (ex instanceof RuntimeException) { - throw (RuntimeException) ex; - } - if (ex instanceof Error) { - throw (Error) ex; - } - throw new UndeclaredThrowableException(ex); - } - - /** - * Get the field represented by the supplied {@link Field field object} on the - * specified {@link Object target object}. In accordance with {@link Field#get(Object)} - * semantics, the returned value is automatically wrapped if the underlying field - * has a primitive type. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException(Exception)}. - * - * @param field the field to get - * @param target the target object from which to get the field - * - * @return the field's current value - */ - public static Object getField(Field field, Object target) { - try { - return field.get(target); - } catch (IllegalAccessException ex) { - handleReflectionException(ex); - throw new IllegalStateException( - "Unexpected reflection exception - " + ex.getClass().getName() + ": " + ex.getMessage()); - } - } - - /** - * Attempt to find a {@link Method} on the supplied class with the supplied name - * and no parameters. Searches all superclasses up to {@code Object}. - *

Returns {@code null} if no {@link Method} can be found. - * - * @param clazz the class to introspect - * @param name the name of the method - * - * @return the Method object, or {@code null} if none found - */ - public static Method findMethod(Class clazz, String name) { - return findMethod(clazz, name, new Class[0]); - } - - /** - * Attempt to find a {@link Method} on the supplied class with the supplied name - * and parameter types. Searches all superclasses up to {@code Object}. - *

Returns {@code null} if no {@link Method} can be found. - * - * @param clazz the class to introspect - * @param name the name of the method - * @param paramTypes the parameter types of the method - * (may be {@code null} to indicate any signature) - * - * @return the Method object, or {@code null} if none found - */ - public static Method findMethod(Class clazz, String name, Class... paramTypes) { - Assert.notNull(clazz, "Class must not be null"); - Assert.notNull(name, "Method name must not be null"); - Class searchType = clazz; - while (searchType != null) { - Method[] methods = (searchType.isInterface() ? searchType.getMethods() : getDeclaredMethods(searchType)); - for (Method method : methods) { - if (name.equals(method.getName()) && - (paramTypes == null || Arrays.equals(paramTypes, method.getParameterTypes()))) { - return method; - } - } - searchType = searchType.getSuperclass(); - } - return null; - } - - /** - * This variant retrieves {@link Class#getDeclaredMethods()} from a local cache - * in order to avoid the JVM's SecurityManager check and defensive array copying. - * In addition, it also includes Java 8 default methods from locally implemented - * interfaces, since those are effectively to be treated just like declared methods. - * - * @param clazz the class to introspect - * - * @return the cached array of methods - * @throws IllegalStateException if introspection fails - * @see Class#getDeclaredMethods() - */ - private static Method[] getDeclaredMethods(Class clazz) { - Assert.notNull(clazz, "Class must not be null"); - Method[] result = declaredMethodsCache.get(clazz); - if (result == null) { - try { - Method[] declaredMethods = clazz.getDeclaredMethods(); - List defaultMethods = findConcreteMethodsOnInterfaces(clazz); - if (defaultMethods != null) { - result = new Method[declaredMethods.length + defaultMethods.size()]; - System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length); - int index = declaredMethods.length; - for (Method defaultMethod : defaultMethods) { - result[index] = defaultMethod; - index++; - } - } else { - result = declaredMethods; - } - declaredMethodsCache.put(clazz, (result.length == 0 ? NO_METHODS : result)); - } catch (Throwable ex) { - throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() + - "] from ClassLoader [" + clazz.getClassLoader() + "]", ex); - } - } - return result; - } - - private static List findConcreteMethodsOnInterfaces(Class clazz) { - List result = null; - for (Class ifc : clazz.getInterfaces()) { - for (Method ifcMethod : ifc.getMethods()) { - if (!Modifier.isAbstract(ifcMethod.getModifiers())) { - if (result == null) { - result = new LinkedList<>(); - } - result.add(ifcMethod); - } - } - } - return result; - } - - /** - * Invoke the specified {@link Method} against the supplied target object with no arguments. - * The target object can be {@code null} when invoking a static {@link Method}. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. - * - * @param method the method to invoke - * @param target the target object to invoke the method on - * - * @return the invocation result, if any - * @see #invokeMethod(Method, Object, Object[]) - */ - public static Object invokeMethod(Method method, Object target) { - return invokeMethod(method, target, new Object[0]); - } - - /** - * Invoke the specified {@link Method} against the supplied target object with the - * supplied arguments. The target object can be {@code null} when invoking a - * static {@link Method}. - *

Thrown exceptions are handled via a call to {@link #handleReflectionException}. - * - * @param method the method to invoke - * @param target the target object to invoke the method on - * @param args the invocation arguments (may be {@code null}) - * - * @return the invocation result, if any - */ - public static Object invokeMethod(Method method, Object target, Object... args) { - try { - return method.invoke(target, args); - } catch (Exception ex) { - handleReflectionException(ex); - } - throw new IllegalStateException("Should never get here"); - } - - /** - * Invoke the specified JDBC API {@link Method} against the supplied target - * object with no arguments. - * - * @param method the method to invoke - * @param target the target object to invoke the method on - * - * @return the invocation result, if any - * @throws SQLException the JDBC API SQLException to rethrow (if any) - * @see #invokeJdbcMethod(Method, Object, Object[]) - */ - public static Object invokeJdbcMethod(Method method, Object target) throws SQLException { - return invokeJdbcMethod(method, target, new Object[0]); - } - - /** - * Invoke the specified JDBC API {@link Method} against the supplied target - * object with the supplied arguments. - * - * @param method the method to invoke - * @param target the target object to invoke the method on - * @param args the invocation arguments (may be {@code null}) - * - * @return the invocation result, if any - * @throws SQLException the JDBC API SQLException to rethrow (if any) - * @see #invokeMethod(Method, Object, Object[]) - */ - public static Object invokeJdbcMethod(Method method, Object target, Object... args) - throws SQLException { - try { - return method.invoke(target, args); - } catch (IllegalAccessException ex) { - handleReflectionException(ex); - } catch (InvocationTargetException ex) { - if (ex.getTargetException() instanceof SQLException) { - throw (SQLException) ex.getTargetException(); - } - handleInvocationTargetException(ex); - } - throw new IllegalStateException("Should never get here"); - } - - /** - * Rethrow the given {@link Throwable exception}, which is presumably the - * target exception of an {@link InvocationTargetException}. - * Should only be called if no checked exception is expected to be thrown - * by the target method. - *

Rethrows the underlying exception cast to an {@link Exception} or - * {@link Error} if appropriate; otherwise, throws an - * {@link UndeclaredThrowableException}. - * - * @param ex the exception to rethrow - * - * @throws Exception the rethrown exception (in case of a checked exception) - */ - public static void rethrowException(Throwable ex) throws Exception { - if (ex instanceof Exception) { - throw (Exception) ex; - } - if (ex instanceof Error) { - throw (Error) ex; - } - throw new UndeclaredThrowableException(ex); - } - - /** - * Determine whether the given method explicitly declares the given - * exception or one of its superclasses, which means that an exception - * of that type can be propagated as-is within a reflective invocation. - * - * @param method the declaring method - * @param exceptionType the exception to throw - * - * @return {@code true} if the exception can be thrown as-is; - * {@code false} if it needs to be wrapped - */ - public static boolean declaresException(Method method, Class exceptionType) { - Assert.notNull(method, "Method must not be null"); - Class[] declaredExceptions = method.getExceptionTypes(); - for (Class declaredException : declaredExceptions) { - if (declaredException.isAssignableFrom(exceptionType)) { - return true; - } - } - return false; - } - - /** - * Determine whether the given field is a "public static final" constant. - * - * @param field the field to check - */ - public static boolean isPublicStaticFinal(Field field) { - int modifiers = field.getModifiers(); - return (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)); - } - - /** - * Determine whether the given method is an "equals" method. - * - * @see Object#equals(Object) - */ - public static boolean isEqualsMethod(Method method) { - if (method == null || !method.getName().equals("equals")) { - return false; - } - Class[] paramTypes = method.getParameterTypes(); - return (paramTypes.length == 1 && paramTypes[0] == Object.class); - } - - /** - * Determine whether the given method is a "hashCode" method. - * - * @see Object#hashCode() - */ - public static boolean isHashCodeMethod(Method method) { - return (method != null && method.getName().equals("hashCode") && method.getParameterCount() == 0); - } - - /** - * Determine whether the given method is a "toString" method. - * - * @see Object#toString() - */ - public static boolean isToStringMethod(Method method) { - return (method != null && method.getName().equals("toString") && method.getParameterCount() == 0); - } - - /** - * Determine whether the given method is originally declared by {@link Object}. - */ - public static boolean isObjectMethod(Method method) { - if (method == null) { - return false; - } - try { - Object.class.getDeclaredMethod(method.getName(), method.getParameterTypes()); - return true; - } catch (Exception ex) { - return false; - } - } - - /** - * Determine whether the given method is a CGLIB 'renamed' method, - * following the pattern "CGLIB$methodName$0". - * - * @param renamedMethod the method to check - * - * @see org.springframework.cglib.proxy.Enhancer#rename - */ - public static boolean isCglibRenamedMethod(Method renamedMethod) { - String name = renamedMethod.getName(); - if (name.startsWith(CGLIB_RENAMED_METHOD_PREFIX)) { - int i = name.length() - 1; - while (i >= 0 && Character.isDigit(name.charAt(i))) { - i--; - } - return ((i > CGLIB_RENAMED_METHOD_PREFIX.length()) && - (i < name.length() - 1) && name.charAt(i) == '$'); - } - return false; - } - - /** - * Make the given method accessible, explicitly setting it accessible if - * necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts with a JVM - * SecurityManager (if active). - * - * @param method the method to make accessible - * - * @see Method#setAccessible - */ - @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Method method) { - if ((!Modifier.isPublic(method.getModifiers()) || - !Modifier.isPublic(method.getDeclaringClass().getModifiers())) && !method.isAccessible()) { - method.setAccessible(true); - } - } - - /** - * Obtain an accessible constructor for the given class and parameters. - * - * @param clazz the clazz to check - * @param parameterTypes the parameter types of the desired constructor - * - * @return the constructor reference - * @throws NoSuchMethodException if no such constructor exists - * @since 5.0 - */ - public static Constructor accessibleConstructor(Class clazz, Class... parameterTypes) - throws NoSuchMethodException { - - Constructor ctor = clazz.getDeclaredConstructor(parameterTypes); - makeAccessible(ctor); - return ctor; - } - - /** - * Make the given constructor accessible, explicitly setting it accessible - * if necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts with a JVM - * SecurityManager (if active). - * - * @param ctor the constructor to make accessible - * - * @see Constructor#setAccessible - */ - @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Constructor ctor) { - if ((!Modifier.isPublic(ctor.getModifiers()) || - !Modifier.isPublic(ctor.getDeclaringClass().getModifiers())) && !ctor.isAccessible()) { - ctor.setAccessible(true); - } - } - - /** - * Perform the given callback operation on all matching methods of the given - * class, as locally declared or equivalent thereof (such as default methods - * on Java 8 based interfaces that the given class implements). - * - * @param clazz the class to introspect - * @param mc the callback to invoke for each method - * - * @throws IllegalStateException if introspection fails - * @see #doWithMethods - * @since 4.2 - */ - public static void doWithLocalMethods(Class clazz, MethodCallback mc) { - Method[] methods = getDeclaredMethods(clazz); - for (Method method : methods) { - try { - mc.doWith(method); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); - } - } - } - - /** - * Perform the given callback operation on all matching methods of the given - * class and superclasses. - *

The same named method occurring on subclass and superclass will appear - * twice, unless excluded by a {@link MethodFilter}. - * - * @param clazz the class to introspect - * @param mc the callback to invoke for each method - * - * @throws IllegalStateException if introspection fails - * @see #doWithMethods(Class, MethodCallback, MethodFilter) - */ - public static void doWithMethods(Class clazz, MethodCallback mc) { - doWithMethods(clazz, mc, null); - } - - /** - * Perform the given callback operation on all matching methods of the given - * class and superclasses (or given interface and super-interfaces). - *

The same named method occurring on subclass and superclass will appear - * twice, unless excluded by the specified {@link MethodFilter}. - * - * @param clazz the class to introspect - * @param mc the callback to invoke for each method - * @param mf the filter that determines the methods to apply the callback to - * - * @throws IllegalStateException if introspection fails - */ - public static void doWithMethods(Class clazz, MethodCallback mc, MethodFilter mf) { - // Keep backing up the inheritance hierarchy. - Method[] methods = getDeclaredMethods(clazz); - for (Method method : methods) { - if (mf != null && !mf.matches(method)) { - continue; - } - try { - mc.doWith(method); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("Not allowed to access method '" + method.getName() + "': " + ex); - } - } - if (clazz.getSuperclass() != null) { - doWithMethods(clazz.getSuperclass(), mc, mf); - } else if (clazz.isInterface()) { - for (Class superIfc : clazz.getInterfaces()) { - doWithMethods(superIfc, mc, mf); - } - } - } - - /** - * Get all declared methods on the leaf class and all superclasses. - * Leaf class methods are included first. - * - * @param leafClass the class to introspect - * - * @throws IllegalStateException if introspection fails - */ - public static Method[] getAllDeclaredMethods(Class leafClass) { - final List methods = new ArrayList<>(32); - doWithMethods(leafClass, methods::add); - return methods.toArray(new Method[0]); - } - - /** - * Get the unique set of declared methods on the leaf class and all superclasses. - * Leaf class methods are included first and while traversing the superclass hierarchy - * any methods found with signatures matching a method already included are filtered out. - * - * @param leafClass the class to introspect - * - * @throws IllegalStateException if introspection fails - */ - public static Method[] getUniqueDeclaredMethods(Class leafClass) { - final List methods = new ArrayList<>(32); - doWithMethods(leafClass, method -> { - boolean knownSignature = false; - Method methodBeingOverriddenWithCovariantReturnType = null; - for (Method existingMethod : methods) { - if (method.getName().equals(existingMethod.getName()) && - Arrays.equals(method.getParameterTypes(), existingMethod.getParameterTypes())) { - // Is this a covariant return type situation? - if (existingMethod.getReturnType() != method.getReturnType() && - existingMethod.getReturnType().isAssignableFrom(method.getReturnType())) { - methodBeingOverriddenWithCovariantReturnType = existingMethod; - } else { - knownSignature = true; - } - break; - } - } - if (methodBeingOverriddenWithCovariantReturnType != null) { - methods.remove(methodBeingOverriddenWithCovariantReturnType); - } - if (!knownSignature && !isCglibRenamedMethod(method)) { - methods.add(method); - } - }); - return methods.toArray(new Method[0]); - } - - /** - * Invoke the given callback on all locally declared fields in the given class. - * - * @param clazz the target class to analyze - * @param fc the callback to invoke for each field - * - * @throws IllegalStateException if introspection fails - * @see #doWithFields - * @since 4.2 - */ - public static void doWithLocalFields(Class clazz, FieldCallback fc) { - for (Field field : getDeclaredFields(clazz)) { - try { - fc.doWith(field); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); - } - } - } - - /** - * Invoke the given callback on all fields in the target class, going up the - * class hierarchy to get all declared fields. - * - * @param clazz the target class to analyze - * @param fc the callback to invoke for each field - * - * @throws IllegalStateException if introspection fails - */ - public static void doWithFields(Class clazz, FieldCallback fc) { - doWithFields(clazz, fc, null); - } - - /** - * Invoke the given callback on all fields in the target class, going up the - * class hierarchy to get all declared fields. - * - * @param clazz the target class to analyze - * @param fc the callback to invoke for each field - * @param ff the filter that determines the fields to apply the callback to - * - * @throws IllegalStateException if introspection fails - */ - public static void doWithFields(Class clazz, FieldCallback fc, FieldFilter ff) { - // Keep backing up the inheritance hierarchy. - Class targetClass = clazz; - do { - Field[] fields = getDeclaredFields(targetClass); - for (Field field : fields) { - if (ff != null && !ff.matches(field)) { - continue; - } - try { - fc.doWith(field); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("Not allowed to access field '" + field.getName() + "': " + ex); - } - } - targetClass = targetClass.getSuperclass(); - } - while (targetClass != null && targetClass != Object.class); - } - - /** - * Given the source object and the destination, which must be the same class - * or a subclass, copy all fields, including inherited fields. Designed to - * work on objects with public no-arg constructors. - * - * @throws IllegalStateException if introspection fails - */ - public static void shallowCopyFieldState(final Object src, final Object dest) { - Assert.notNull(src, "Source for field copy cannot be null"); - Assert.notNull(dest, "Destination for field copy cannot be null"); - if (!src.getClass().isAssignableFrom(dest.getClass())) { - throw new IllegalArgumentException("Destination class [" + dest.getClass().getName() + - "] must be same or subclass as source class [" + src.getClass() - .getName() + "]"); - } - doWithFields(src.getClass(), field -> { - makeAccessible(field); - Object srcValue = field.get(src); - field.set(dest, srcValue); - }, COPYABLE_FIELDS); - } - - /** - * Make the given field accessible, explicitly setting it accessible if - * necessary. The {@code setAccessible(true)} method is only called - * when actually necessary, to avoid unnecessary conflicts with a JVM - * SecurityManager (if active). - * - * @param field the field to make accessible - * - * @see Field#setAccessible - */ - @SuppressWarnings("deprecation") // on JDK 9 - public static void makeAccessible(Field field) { - if ((!Modifier.isPublic(field.getModifiers()) || - !Modifier.isPublic(field.getDeclaringClass().getModifiers()) || - Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) { - field.setAccessible(true); - } - } - - /** - * Clear the internal method/field cache. - * - * @since 4.2.4 - */ - public static void clearCache() { - declaredMethodsCache.clear(); - declaredFieldsCache.clear(); - } - - - /** - * Action to take on each method. - */ - @FunctionalInterface - public interface MethodCallback { - - /** - * Perform an operation using the given method. - * - * @param method the method to operate on - */ - void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; - } - - - /** - * Callback optionally used to filter methods to be operated on by a method callback. - */ - @FunctionalInterface - public interface MethodFilter { - - /** - * Determine whether the given method matches. - * - * @param method the method to check - */ - boolean matches(Method method); - } - - - /** - * Callback interface invoked on each field in the hierarchy. - */ - @FunctionalInterface - public interface FieldCallback { - - /** - * Perform an operation using the given field. - * - * @param field the field to operate on - */ - void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; - } - - - /** - * Callback optionally used to filter fields to be operated on by a field callback. - */ - @FunctionalInterface - public interface FieldFilter { - - /** - * Determine whether the given field matches. - * - * @param field the field to check - */ - boolean matches(Field field); - } - -} diff --git a/src/main/java/com/antkorwin/xsync/springframework/util/StringUtils.java b/src/main/java/com/antkorwin/xsync/springframework/util/StringUtils.java deleted file mode 100644 index e4a935d..0000000 --- a/src/main/java/com/antkorwin/xsync/springframework/util/StringUtils.java +++ /dev/null @@ -1,1392 +0,0 @@ -/* - * Copyright 2002-2018 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 - * - * 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.antkorwin.xsync.springframework.util; - -import java.io.ByteArrayOutputStream; -import java.nio.charset.Charset; -import java.util.*; - -/** - * Miscellaneous {@link String} utility methods. - *

- *

Mainly for internal use within the framework; consider - * Apache's Commons Lang - * for a more comprehensive suite of {@code String} utilities. - *

- *

This class delivers some simple functionality that should really be - * provided by the core Java {@link String} and {@link StringBuilder} - * classes. It also provides easy-to-use methods to convert between - * delimited strings, such as CSV strings, and collections and arrays. - * - * @author Rod Johnson - * @author Juergen Hoeller - * @author Keith Donald - * @author Rob Harrop - * @author Rick Evans - * @author Arjen Poutsma - * @author Sam Brannen - * @author Brian Clozel - * @since 16 April 2001 - */ -public abstract class StringUtils { - - private static final String FOLDER_SEPARATOR = "/"; - - private static final String WINDOWS_FOLDER_SEPARATOR = "\\"; - - private static final String TOP_PATH = ".."; - - private static final String CURRENT_PATH = "."; - - private static final char EXTENSION_SEPARATOR = '.'; - - - //--------------------------------------------------------------------- - // General convenience methods for working with Strings - //--------------------------------------------------------------------- - - /** - * Check whether the given {@code String} is empty. - *

This method accepts any Object as an argument, comparing it to - * {@code null} and the empty String. As a consequence, this method - * will never return {@code true} for a non-null non-String object. - *

The Object signature is useful for general attribute handling code - * that commonly deals with Strings but generally has to iterate over - * Objects since attributes may e.g. be primitive value objects as well. - * - * @param str the candidate String - * - * @since 3.2.1 - */ - public static boolean isEmpty(Object str) { - return (str == null || "".equals(str)); - } - - /** - * Check whether the given {@code CharSequence} contains actual text. - *

More specifically, this method returns {@code true} if the - * {@code CharSequence} is not {@code null}, its length is greater than - * 0, and it contains at least one non-whitespace character. - *

-     * StringUtils.hasText(null) = false
-     * StringUtils.hasText("") = false
-     * StringUtils.hasText(" ") = false
-     * StringUtils.hasText("12345") = true
-     * StringUtils.hasText(" 12345 ") = true
-     * 
- * - * @param str the {@code CharSequence} to check (may be {@code null}) - * - * @return {@code true} if the {@code CharSequence} is not {@code null}, - * its length is greater than 0, and it does not contain whitespace only - * @see Character#isWhitespace - */ - public static boolean hasText(CharSequence str) { - return (str != null && str.length() > 0 && containsText(str)); - } - - private static boolean containsText(CharSequence str) { - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (!Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; - } - - /** - * Check whether the given {@code String} contains any whitespace characters. - * - * @param str the {@code String} to check (may be {@code null}) - * - * @return {@code true} if the {@code String} is not empty and - * contains at least 1 whitespace character - * @see #containsWhitespace(CharSequence) - */ - public static boolean containsWhitespace(String str) { - return containsWhitespace((CharSequence) str); - } - - /** - * Check whether the given {@code CharSequence} contains any whitespace characters. - * - * @param str the {@code CharSequence} to check (may be {@code null}) - * - * @return {@code true} if the {@code CharSequence} is not empty and - * contains at least 1 whitespace character - * @see Character#isWhitespace - */ - public static boolean containsWhitespace(CharSequence str) { - if (!hasLength(str)) { - return false; - } - - int strLen = str.length(); - for (int i = 0; i < strLen; i++) { - if (Character.isWhitespace(str.charAt(i))) { - return true; - } - } - return false; - } - - /** - * Check that the given {@code CharSequence} is neither {@code null} nor - * of length 0. - *

Note: this method returns {@code true} for a {@code CharSequence} - * that purely consists of whitespace. - *

-     * StringUtils.hasLength(null) = false
-     * StringUtils.hasLength("") = false
-     * StringUtils.hasLength(" ") = true
-     * StringUtils.hasLength("Hello") = true
-     * 
- * - * @param str the {@code CharSequence} to check (may be {@code null}) - * - * @return {@code true} if the {@code CharSequence} is not {@code null} and has length - * @see #hasText(String) - */ - public static boolean hasLength(CharSequence str) { - return (str != null && str.length() > 0); - } - - /** - * Trim leading and trailing whitespace from the given {@code String}. - * - * @param str the {@code String} to check - * - * @return the trimmed {@code String} - * @see Character#isWhitespace - */ - public static String trimWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - - int beginIndex = 0; - int endIndex = str.length() - 1; - - while (beginIndex <= endIndex && Character.isWhitespace(str.charAt(beginIndex))) { - beginIndex++; - } - - while (endIndex > beginIndex && Character.isWhitespace(str.charAt(endIndex))) { - endIndex--; - } - - return str.substring(beginIndex, endIndex + 1); - } - - /** - * Check that the given {@code String} is neither {@code null} nor of length 0. - *

Note: this method returns {@code true} for a {@code String} that - * purely consists of whitespace. - * - * @param str the {@code String} to check (may be {@code null}) - * - * @return {@code true} if the {@code String} is not {@code null} and has length - * @see #hasLength(CharSequence) - * @see #hasText(String) - */ - public static boolean hasLength(String str) { - return (str != null && !str.isEmpty()); - } - - /** - * Trim all whitespace from the given {@code String}: - * leading, trailing, and in between characters. - * - * @param str the {@code String} to check - * - * @return the trimmed {@code String} - * @see Character#isWhitespace - */ - public static String trimAllWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - - int len = str.length(); - StringBuilder sb = new StringBuilder(str.length()); - for (int i = 0; i < len; i++) { - char c = str.charAt(i); - if (!Character.isWhitespace(c)) { - sb.append(c); - } - } - return sb.toString(); - } - - /** - * Trim trailing whitespace from the given {@code String}. - * - * @param str the {@code String} to check - * - * @return the trimmed {@code String} - * @see Character#isWhitespace - */ - public static String trimTrailingWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && Character.isWhitespace(sb.charAt(sb.length() - 1))) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - - /** - * Trim all occurrences of the supplied trailing character from the given {@code String}. - * - * @param str the {@code String} to check - * @param trailingCharacter the trailing character to be trimmed - * - * @return the trimmed {@code String} - */ - public static String trimTrailingCharacter(String str, char trailingCharacter) { - if (!hasLength(str)) { - return str; - } - - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && sb.charAt(sb.length() - 1) == trailingCharacter) { - sb.deleteCharAt(sb.length() - 1); - } - return sb.toString(); - } - - /** - * Test if the given {@code String} starts with the specified prefix, - * ignoring upper/lower case. - * - * @param str the {@code String} to check - * @param prefix the prefix to look for - * - * @see String#startsWith - */ - public static boolean startsWithIgnoreCase(String str, String prefix) { - return (str != null && prefix != null && str.length() >= prefix.length() && - str.regionMatches(true, 0, prefix, 0, prefix.length())); - } - - /** - * Test if the given {@code String} ends with the specified suffix, - * ignoring upper/lower case. - * - * @param str the {@code String} to check - * @param suffix the suffix to look for - * - * @see String#endsWith - */ - public static boolean endsWithIgnoreCase(String str, String suffix) { - return (str != null && suffix != null && str.length() >= suffix.length() && - str.regionMatches(true, str.length() - suffix.length(), suffix, 0, suffix.length())); - } - - /** - * Test whether the given string matches the given substring - * at the given index. - * - * @param str the original string (or StringBuilder) - * @param index the index in the original string to start matching against - * @param substring the substring to match at the given index - */ - public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { - if (index + substring.length() > str.length()) { - return false; - } - for (int i = 0; i < substring.length(); i++) { - if (str.charAt(index + i) != substring.charAt(i)) { - return false; - } - } - return true; - } - - /** - * Count the occurrences of the substring {@code sub} in string {@code str}. - * - * @param str string to search in - * @param sub string to search for - */ - public static int countOccurrencesOf(String str, String sub) { - if (!hasLength(str) || !hasLength(sub)) { - return 0; - } - - int count = 0; - int pos = 0; - int idx; - while ((idx = str.indexOf(sub, pos)) != -1) { - ++count; - pos = idx + sub.length(); - } - return count; - } - - /** - * Delete all occurrences of the given substring. - * - * @param inString the original {@code String} - * @param pattern the pattern to delete all occurrences of - * - * @return the resulting {@code String} - */ - public static String delete(String inString, String pattern) { - return replace(inString, pattern, ""); - } - - /** - * Replace all occurrences of a substring within a string with another string. - * - * @param inString {@code String} to examine - * @param oldPattern {@code String} to replace - * @param newPattern {@code String} to insert - * - * @return a {@code String} with the replacements - */ - public static String replace(String inString, String oldPattern, String newPattern) { - if (!hasLength(inString) || !hasLength(oldPattern) || newPattern == null) { - return inString; - } - int index = inString.indexOf(oldPattern); - if (index == -1) { - // no occurrence -> can return input as-is - return inString; - } - - int capacity = inString.length(); - if (newPattern.length() > oldPattern.length()) { - capacity += 16; - } - StringBuilder sb = new StringBuilder(capacity); - - int pos = 0; // our position in the old string - int patLen = oldPattern.length(); - while (index >= 0) { - sb.append(inString.substring(pos, index)); - sb.append(newPattern); - pos = index + patLen; - index = inString.indexOf(oldPattern, pos); - } - - // append any characters to the right of a match - sb.append(inString.substring(pos)); - return sb.toString(); - } - - /** - * Turn the given Object into a {@code String} with single quotes - * if it is a {@code String}; keeping the Object as-is else. - * - * @param obj the input Object (e.g. "myString") - * - * @return the quoted {@code String} (e.g. "'myString'"), - * or the input object as-is if not a {@code String} - */ - public static Object quoteIfString(Object obj) { - return (obj instanceof String ? quote((String) obj) : obj); - } - - /** - * Quote the given {@code String} with single quotes. - * - * @param str the input {@code String} (e.g. "myString") - * - * @return the quoted {@code String} (e.g. "'myString'"), - * or {@code null} if the input was {@code null} - */ - public static String quote(String str) { - return (str != null ? "'" + str + "'" : null); - } - - /** - * Unqualify a string qualified by a '.' dot character. For example, - * "this.name.is.qualified", returns "qualified". - * - * @param qualifiedName the qualified name - */ - public static String unqualify(String qualifiedName) { - return unqualify(qualifiedName, '.'); - } - - /** - * Unqualify a string qualified by a separator character. For example, - * "this:name:is:qualified" returns "qualified" if using a ':' separator. - * - * @param qualifiedName the qualified name - * @param separator the separator - */ - public static String unqualify(String qualifiedName, char separator) { - return qualifiedName.substring(qualifiedName.lastIndexOf(separator) + 1); - } - - - //--------------------------------------------------------------------- - // Convenience methods for working with formatted Strings - //--------------------------------------------------------------------- - - /** - * Capitalize a {@code String}, changing the first letter to - * upper case as per {@link Character#toUpperCase(char)}. - * No other letters are changed. - * - * @param str the {@code String} to capitalize - * - * @return the capitalized {@code String} - */ - public static String capitalize(String str) { - return changeFirstCharacterCase(str, true); - } - - private static String changeFirstCharacterCase(String str, boolean capitalize) { - if (!hasLength(str)) { - return str; - } - - char baseChar = str.charAt(0); - char updatedChar; - if (capitalize) { - updatedChar = Character.toUpperCase(baseChar); - } else { - updatedChar = Character.toLowerCase(baseChar); - } - if (baseChar == updatedChar) { - return str; - } - - char[] chars = str.toCharArray(); - chars[0] = updatedChar; - return new String(chars, 0, chars.length); - } - - /** - * Uncapitalize a {@code String}, changing the first letter to - * lower case as per {@link Character#toLowerCase(char)}. - * No other letters are changed. - * - * @param str the {@code String} to uncapitalize - * - * @return the uncapitalized {@code String} - */ - public static String uncapitalize(String str) { - return changeFirstCharacterCase(str, false); - } - - /** - * Extract the filename from the given Java resource path, - * e.g. {@code "mypath/myfile.txt" -> "myfile.txt"}. - * - * @param path the file path (may be {@code null}) - * - * @return the extracted filename, or {@code null} if none - */ - public static String getFilename(String path) { - if (path == null) { - return null; - } - - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - return (separatorIndex != -1 ? path.substring(separatorIndex + 1) : path); - } - - /** - * Extract the filename extension from the given Java resource path, - * e.g. "mypath/myfile.txt" -> "txt". - * - * @param path the file path (may be {@code null}) - * - * @return the extracted filename extension, or {@code null} if none - */ - public static String getFilenameExtension(String path) { - if (path == null) { - return null; - } - - int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - if (extIndex == -1) { - return null; - } - - int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (folderIndex > extIndex) { - return null; - } - - return path.substring(extIndex + 1); - } - - /** - * Strip the filename extension from the given Java resource path, - * e.g. "mypath/myfile.txt" -> "mypath/myfile". - * - * @param path the file path - * - * @return the path with stripped filename extension - */ - public static String stripFilenameExtension(String path) { - int extIndex = path.lastIndexOf(EXTENSION_SEPARATOR); - if (extIndex == -1) { - return path; - } - - int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (folderIndex > extIndex) { - return path; - } - - return path.substring(0, extIndex); - } - - /** - * Apply the given relative path to the given Java resource path, - * assuming standard Java folder separation (i.e. "/" separators). - * - * @param path the path to start from (usually a full file path) - * @param relativePath the relative path to apply - * (relative to the full file path above) - * - * @return the full file path that results from applying the relative path - */ - public static String applyRelativePath(String path, String relativePath) { - int separatorIndex = path.lastIndexOf(FOLDER_SEPARATOR); - if (separatorIndex != -1) { - String newPath = path.substring(0, separatorIndex); - if (!relativePath.startsWith(FOLDER_SEPARATOR)) { - newPath += FOLDER_SEPARATOR; - } - return newPath + relativePath; - } else { - return relativePath; - } - } - - /** - * Compare two paths after normalization of them. - * - * @param path1 first path for comparison - * @param path2 second path for comparison - * - * @return whether the two paths are equivalent after normalization - */ - public static boolean pathEquals(String path1, String path2) { - return cleanPath(path1).equals(cleanPath(path2)); - } - - /** - * Normalize the path by suppressing sequences like "path/.." and - * inner simple dots. - *

The result is convenient for path comparison. For other uses, - * notice that Windows separators ("\") are replaced by simple slashes. - * - * @param path the original path - * - * @return the normalized path - */ - public static String cleanPath(String path) { - if (!hasLength(path)) { - return path; - } - String pathToUse = replace(path, WINDOWS_FOLDER_SEPARATOR, FOLDER_SEPARATOR); - - // Strip prefix from path to analyze, to not treat it as part of the - // first path element. This is necessary to correctly parse paths like - // "file:core/../core/io/Resource.class", where the ".." should just - // strip the first "core" directory while keeping the "file:" prefix. - int prefixIndex = pathToUse.indexOf(':'); - String prefix = ""; - if (prefixIndex != -1) { - prefix = pathToUse.substring(0, prefixIndex + 1); - if (prefix.contains("/")) { - prefix = ""; - } else { - pathToUse = pathToUse.substring(prefixIndex + 1); - } - } - if (pathToUse.startsWith(FOLDER_SEPARATOR)) { - prefix = prefix + FOLDER_SEPARATOR; - pathToUse = pathToUse.substring(1); - } - - String[] pathArray = delimitedListToStringArray(pathToUse, FOLDER_SEPARATOR); - List pathElements = new LinkedList<>(); - int tops = 0; - - for (int i = pathArray.length - 1; i >= 0; i--) { - String element = pathArray[i]; - if (CURRENT_PATH.equals(element)) { - // Points to current directory - drop it. - } else if (TOP_PATH.equals(element)) { - // Registering top path found. - tops++; - } else { - if (tops > 0) { - // Merging path element with element corresponding to top path. - tops--; - } else { - // Normal path element found. - pathElements.add(0, element); - } - } - } - - // Remaining top paths need to be retained. - for (int i = 0; i < tops; i++) { - pathElements.add(0, TOP_PATH); - } - - return prefix + collectionToDelimitedString(pathElements, FOLDER_SEPARATOR); - } - - /** - * Take a {@code String} that is a delimited list and convert it into a - * {@code String} array. - *

A single {@code delimiter} may consist of more than one character, - * but it will still be considered as a single delimiter string, rather - * than as bunch of potential delimiter characters, in contrast to - * {@link #tokenizeToStringArray}. - * - * @param str the input {@code String} - * @param delimiter the delimiter between elements (this is a single delimiter, - * rather than a bunch individual delimiter characters) - * - * @return an array of the tokens in the list - * @see #tokenizeToStringArray - */ - public static String[] delimitedListToStringArray(String str, String delimiter) { - return delimitedListToStringArray(str, delimiter, null); - } - - /** - * Convert a {@code Collection} into a delimited {@code String} (e.g. CSV). - *

Useful for {@code toString()} implementations. - * - * @param coll the {@code Collection} to convert - * @param delim the delimiter to use (typically a ",") - * - * @return the delimited {@code String} - */ - public static String collectionToDelimitedString(Collection coll, String delim) { - return collectionToDelimitedString(coll, delim, "", ""); - } - - /** - * Take a {@code String} that is a delimited list and convert it into - * a {@code String} array. - *

A single {@code delimiter} may consist of more than one character, - * but it will still be considered as a single delimiter string, rather - * than as bunch of potential delimiter characters, in contrast to - * {@link #tokenizeToStringArray}. - * - * @param str the input {@code String} - * @param delimiter the delimiter between elements (this is a single delimiter, - * rather than a bunch individual delimiter characters) - * @param charsToDelete a set of characters to delete; useful for deleting unwanted - * line breaks: e.g. "\r\n\f" will delete all new lines and line feeds in a {@code String} - * - * @return an array of the tokens in the list - * @see #tokenizeToStringArray - */ - public static String[] delimitedListToStringArray( - String str, String delimiter, String charsToDelete) { - - if (str == null) { - return new String[0]; - } - if (delimiter == null) { - return new String[]{str}; - } - - List result = new ArrayList<>(); - if ("".equals(delimiter)) { - for (int i = 0; i < str.length(); i++) { - result.add(deleteAny(str.substring(i, i + 1), charsToDelete)); - } - } else { - int pos = 0; - int delPos; - while ((delPos = str.indexOf(delimiter, pos)) != -1) { - result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); - pos = delPos + delimiter.length(); - } - if (str.length() > 0 && pos <= str.length()) { - // Add rest of String, but not in case of empty input. - result.add(deleteAny(str.substring(pos), charsToDelete)); - } - } - return toStringArray(result); - } - - /** - * Convert a {@link Collection} to a delimited {@code String} (e.g. CSV). - *

Useful for {@code toString()} implementations. - * - * @param coll the {@code Collection} to convert - * @param delim the delimiter to use (typically a ",") - * @param prefix the {@code String} to start each element with - * @param suffix the {@code String} to end each element with - * - * @return the delimited {@code String} - */ - public static String collectionToDelimitedString( - Collection coll, String delim, String prefix, String suffix) { - - if (CollectionUtils.isEmpty(coll)) { - return ""; - } - - StringBuilder sb = new StringBuilder(); - Iterator it = coll.iterator(); - while (it.hasNext()) { - sb.append(prefix).append(it.next()).append(suffix); - if (it.hasNext()) { - sb.append(delim); - } - } - return sb.toString(); - } - - /** - * Delete any character in a given {@code String}. - * - * @param inString the original {@code String} - * @param charsToDelete a set of characters to delete. - * E.g. "az\n" will delete 'a's, 'z's and new lines. - * - * @return the resulting {@code String} - */ - public static String deleteAny(String inString, String charsToDelete) { - if (!hasLength(inString) || !hasLength(charsToDelete)) { - return inString; - } - - StringBuilder sb = new StringBuilder(inString.length()); - for (int i = 0; i < inString.length(); i++) { - char c = inString.charAt(i); - if (charsToDelete.indexOf(c) == -1) { - sb.append(c); - } - } - return sb.toString(); - } - - /** - * Copy the given {@code Collection} into a {@code String} array. - *

The {@code Collection} must contain {@code String} elements only. - * - * @param collection the {@code Collection} to copy - * - * @return the {@code String} array - */ - public static String[] toStringArray(Collection collection) { - return collection.toArray(new String[0]); - } - - /** - * Decode the given encoded URI component value. Based on the following rules: - *

    - *
  • Alphanumeric characters {@code "a"} through {@code "z"}, {@code "A"} through {@code "Z"}, - * and {@code "0"} through {@code "9"} stay the same.
  • - *
  • Special characters {@code "-"}, {@code "_"}, {@code "."}, and {@code "*"} stay the same.
  • - *
  • A sequence "{@code %xy}" is interpreted as a hexadecimal representation of the character.
  • - *
- * - * @param source the encoded String - * @param charset the character set - * - * @return the decoded value - * @throws IllegalArgumentException when the given source contains invalid encoded sequences - * @see java.net.URLDecoder#decode(String, String) - * @since 5.0 - */ - public static String uriDecode(String source, Charset charset) { - int length = source.length(); - if (length == 0) { - return source; - } - Assert.notNull(charset, "Charset must not be null"); - - ByteArrayOutputStream bos = new ByteArrayOutputStream(length); - boolean changed = false; - for (int i = 0; i < length; i++) { - int ch = source.charAt(i); - if (ch == '%') { - if (i + 2 < length) { - char hex1 = source.charAt(i + 1); - char hex2 = source.charAt(i + 2); - int u = Character.digit(hex1, 16); - int l = Character.digit(hex2, 16); - if (u == -1 || l == -1) { - throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); - } - bos.write((char) ((u << 4) + l)); - i += 2; - changed = true; - } else { - throw new IllegalArgumentException("Invalid encoded sequence \"" + source.substring(i) + "\""); - } - } else { - bos.write(ch); - } - } - return (changed ? new String(bos.toByteArray(), charset) : source); - } - - /** - * Parse the given {@code String} value into a {@link Locale}, accepting - * the {@link Locale#toString} format as well as BCP 47 language tags. - * - * @param localeValue the locale value: following either {@code Locale's} - * {@code toString()} format ("en", "en_UK", etc), also accepting spaces as - * separators (as an alternative to underscores), or BCP 47 (e.g. "en-UK") - * as specified by {@link Locale#forLanguageTag} on Java 7+ - * - * @return a corresponding {@code Locale} instance, or {@code null} if none - * @throws IllegalArgumentException in case of an invalid locale specification - * @see #parseLocaleString - * @see Locale#forLanguageTag - * @since 5.0.4 - */ - public static Locale parseLocale(String localeValue) { - String[] tokens = tokenizeLocaleSource(localeValue); - if (tokens.length == 1) { - return Locale.forLanguageTag(localeValue); - } - return parseLocaleTokens(localeValue, tokens); - } - - private static String[] tokenizeLocaleSource(String localeSource) { - return tokenizeToStringArray(localeSource, "_ ", false, false); - } - - private static Locale parseLocaleTokens(String localeString, String[] tokens) { - String language = (tokens.length > 0 ? tokens[0] : ""); - String country = (tokens.length > 1 ? tokens[1] : ""); - validateLocalePart(language); - validateLocalePart(country); - - String variant = ""; - if (tokens.length > 2) { - // There is definitely a variant, and it is everything after the country - // code sans the separator between the country code and the variant. - int endIndexOfCountryCode = localeString.indexOf(country, language.length()) + country.length(); - // Strip off any leading '_' and whitespace, what's left is the variant. - variant = trimLeadingWhitespace(localeString.substring(endIndexOfCountryCode)); - if (variant.startsWith("_")) { - variant = trimLeadingCharacter(variant, '_'); - } - } - return (language.length() > 0 ? new Locale(language, country, variant) : null); - } - - /** - * Tokenize the given {@code String} into a {@code String} array via a - * {@link StringTokenizer}. - *

The given {@code delimiters} string can consist of any number of - * delimiter characters. Each of those characters can be used to separate - * tokens. A delimiter is always a single character; for multi-character - * delimiters, consider using {@link #delimitedListToStringArray}. - * - * @param str the {@code String} to tokenize - * @param delimiters the delimiter characters, assembled as a {@code String} - * (each of the characters is individually considered as a delimiter) - * @param trimTokens trim the tokens via {@link String#trim()} - * @param ignoreEmptyTokens omit empty tokens from the result array - * (only applies to tokens that are empty after trimming; StringTokenizer - * will not consider subsequent delimiters as token in the first place). - * - * @return an array of the tokens - * @see StringTokenizer - * @see String#trim() - * @see #delimitedListToStringArray - */ - public static String[] tokenizeToStringArray( - String str, String delimiters, boolean trimTokens, boolean ignoreEmptyTokens) { - - if (str == null) { - return new String[0]; - } - - StringTokenizer st = new StringTokenizer(str, delimiters); - List tokens = new ArrayList<>(); - while (st.hasMoreTokens()) { - String token = st.nextToken(); - if (trimTokens) { - token = token.trim(); - } - if (!ignoreEmptyTokens || token.length() > 0) { - tokens.add(token); - } - } - return toStringArray(tokens); - } - - private static void validateLocalePart(String localePart) { - for (int i = 0; i < localePart.length(); i++) { - char ch = localePart.charAt(i); - if (ch != ' ' && ch != '_' && ch != '#' && !Character.isLetterOrDigit(ch)) { - throw new IllegalArgumentException( - "Locale part \"" + localePart + "\" contains invalid characters"); - } - } - } - - - //--------------------------------------------------------------------- - // Convenience methods for working with String arrays - //--------------------------------------------------------------------- - - /** - * Trim leading whitespace from the given {@code String}. - * - * @param str the {@code String} to check - * - * @return the trimmed {@code String} - * @see Character#isWhitespace - */ - public static String trimLeadingWhitespace(String str) { - if (!hasLength(str)) { - return str; - } - - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && Character.isWhitespace(sb.charAt(0))) { - sb.deleteCharAt(0); - } - return sb.toString(); - } - - /** - * Trim all occurrences of the supplied leading character from the given {@code String}. - * - * @param str the {@code String} to check - * @param leadingCharacter the leading character to be trimmed - * - * @return the trimmed {@code String} - */ - public static String trimLeadingCharacter(String str, char leadingCharacter) { - if (!hasLength(str)) { - return str; - } - - StringBuilder sb = new StringBuilder(str); - while (sb.length() > 0 && sb.charAt(0) == leadingCharacter) { - sb.deleteCharAt(0); - } - return sb.toString(); - } - - /** - * Parse the given {@code String} representation into a {@link Locale}. - *

This is the inverse operation of {@link Locale#toString Locale's toString}. - * - * @param localeString the locale {@code String}: following {@code Locale's} - * {@code toString()} format ("en", "en_UK", etc), also accepting spaces as - * separators (as an alternative to underscores) - *

Note: This variant does not accept the BCP 47 language tag format. - * Please use {@link #parseLocale} for lenient parsing of both formats. - * - * @return a corresponding {@code Locale} instance, or {@code null} if none - * @throws IllegalArgumentException in case of an invalid locale specification - */ - public static Locale parseLocaleString(String localeString) { - return parseLocaleTokens(localeString, tokenizeLocaleSource(localeString)); - } - - /** - * Determine the RFC 3066 compliant language tag, - * as used for the HTTP "Accept-Language" header. - * - * @param locale the Locale to transform to a language tag - * - * @return the RFC 3066 compliant language tag as {@code String} - * @deprecated as of 5.0.4, in favor of {@link Locale#toLanguageTag()} - */ - @Deprecated - public static String toLanguageTag(Locale locale) { - return locale.getLanguage() + (hasText(locale.getCountry()) ? "-" + locale.getCountry() : ""); - } - - /** - * Check whether the given {@code String} contains actual text. - *

More specifically, this method returns {@code true} if the - * {@code String} is not {@code null}, its length is greater than 0, - * and it contains at least one non-whitespace character. - * - * @param str the {@code String} to check (may be {@code null}) - * - * @return {@code true} if the {@code String} is not {@code null}, its - * length is greater than 0, and it does not contain whitespace only - * @see #hasText(CharSequence) - */ - public static boolean hasText(String str) { - return (str != null && !str.isEmpty() && containsText(str)); - } - - /** - * Parse the given {@code timeZoneString} value into a {@link TimeZone}. - * - * @param timeZoneString the time zone {@code String}, following {@link TimeZone#getTimeZone(String)} - * but throwing {@link IllegalArgumentException} in case of an invalid time zone specification - * - * @return a corresponding {@link TimeZone} instance - * @throws IllegalArgumentException in case of an invalid time zone specification - */ - public static TimeZone parseTimeZoneString(String timeZoneString) { - TimeZone timeZone = TimeZone.getTimeZone(timeZoneString); - if ("GMT".equals(timeZone.getID()) && !timeZoneString.startsWith("GMT")) { - // We don't want that GMT fallback... - throw new IllegalArgumentException("Invalid time zone specification '" + timeZoneString + "'"); - } - return timeZone; - } - - /** - * Append the given {@code String} to the given {@code String} array, - * returning a new array consisting of the input array contents plus - * the given {@code String}. - * - * @param array the array to append to (can be {@code null}) - * @param str the {@code String} to append - * - * @return the new array (never {@code null}) - */ - public static String[] addStringToArray(String[] array, String str) { - if (ObjectUtils.isEmpty(array)) { - return new String[]{str}; - } - - String[] newArr = new String[array.length + 1]; - System.arraycopy(array, 0, newArr, 0, array.length); - newArr[array.length] = str; - return newArr; - } - - /** - * Concatenate the given {@code String} arrays into one, - * with overlapping array elements included twice. - *

The order of elements in the original arrays is preserved. - * - * @param array1 the first array (can be {@code null}) - * @param array2 the second array (can be {@code null}) - * - * @return the new array ({@code null} if both given arrays were {@code null}) - */ - public static String[] concatenateStringArrays(String[] array1, String[] array2) { - if (ObjectUtils.isEmpty(array1)) { - return array2; - } - if (ObjectUtils.isEmpty(array2)) { - return array1; - } - - String[] newArr = new String[array1.length + array2.length]; - System.arraycopy(array1, 0, newArr, 0, array1.length); - System.arraycopy(array2, 0, newArr, array1.length, array2.length); - return newArr; - } - - /** - * Merge the given {@code String} arrays into one, with overlapping - * array elements only included once. - *

The order of elements in the original arrays is preserved - * (with the exception of overlapping elements, which are only - * included on their first occurrence). - * - * @param array1 the first array (can be {@code null}) - * @param array2 the second array (can be {@code null}) - * - * @return the new array ({@code null} if both given arrays were {@code null}) - * @deprecated as of 4.3.15, in favor of manual merging via {@link LinkedHashSet} - * (with every entry included at most once, even entries within the first array) - */ - @Deprecated - public static String[] mergeStringArrays(String[] array1, String[] array2) { - if (ObjectUtils.isEmpty(array1)) { - return array2; - } - if (ObjectUtils.isEmpty(array2)) { - return array1; - } - - List result = new ArrayList<>(); - result.addAll(Arrays.asList(array1)); - for (String str : array2) { - if (!result.contains(str)) { - result.add(str); - } - } - return toStringArray(result); - } - - /** - * Turn given source {@code String} array into sorted array. - * - * @param array the source array - * - * @return the sorted array (never {@code null}) - */ - public static String[] sortStringArray(String[] array) { - if (ObjectUtils.isEmpty(array)) { - return new String[0]; - } - - Arrays.sort(array); - return array; - } - - /** - * Copy the given Enumeration into a {@code String} array. - * The Enumeration must contain {@code String} elements only. - * - * @param enumeration the Enumeration to copy - * - * @return the {@code String} array - */ - public static String[] toStringArray(Enumeration enumeration) { - return toStringArray(Collections.list(enumeration)); - } - - /** - * Trim the elements of the given {@code String} array, - * calling {@code String.trim()} on each of them. - * - * @param array the original {@code String} array - * - * @return the resulting array (of the same size) with trimmed elements - */ - public static String[] trimArrayElements(String[] array) { - if (ObjectUtils.isEmpty(array)) { - return new String[0]; - } - - String[] result = new String[array.length]; - for (int i = 0; i < array.length; i++) { - String element = array[i]; - result[i] = (element != null ? element.trim() : null); - } - return result; - } - - /** - * Remove duplicate strings from the given array. - *

As of 4.2, it preserves the original order, as it uses a {@link LinkedHashSet}. - * - * @param array the {@code String} array - * - * @return an array without duplicates, in natural sort order - */ - public static String[] removeDuplicateStrings(String[] array) { - if (ObjectUtils.isEmpty(array)) { - return array; - } - - Set set = new LinkedHashSet<>(); - for (String element : array) { - set.add(element); - } - return toStringArray(set); - } - - /** - * Take an array of strings and split each element based on the given delimiter. - * A {@code Properties} instance is then generated, with the left of the delimiter - * providing the key, and the right of the delimiter providing the value. - *

Will trim both the key and value before adding them to the {@code Properties}. - * - * @param array the array to process - * @param delimiter to split each element using (typically the equals symbol) - * - * @return a {@code Properties} instance representing the array contents, - * or {@code null} if the array to process was {@code null} or empty - */ - public static Properties splitArrayElementsIntoProperties(String[] array, String delimiter) { - return splitArrayElementsIntoProperties(array, delimiter, null); - } - - /** - * Take an array of strings and split each element based on the given delimiter. - * A {@code Properties} instance is then generated, with the left of the - * delimiter providing the key, and the right of the delimiter providing the value. - *

Will trim both the key and value before adding them to the - * {@code Properties} instance. - * - * @param array the array to process - * @param delimiter to split each element using (typically the equals symbol) - * @param charsToDelete one or more characters to remove from each element - * prior to attempting the split operation (typically the quotation mark - * symbol), or {@code null} if no removal should occur - * - * @return a {@code Properties} instance representing the array contents, - * or {@code null} if the array to process was {@code null} or empty - */ - public static Properties splitArrayElementsIntoProperties( - String[] array, String delimiter, String charsToDelete) { - - if (ObjectUtils.isEmpty(array)) { - return null; - } - - Properties result = new Properties(); - for (String element : array) { - if (charsToDelete != null) { - element = deleteAny(element, charsToDelete); - } - String[] splittedElement = split(element, delimiter); - if (splittedElement == null) { - continue; - } - result.setProperty(splittedElement[0].trim(), splittedElement[1].trim()); - } - return result; - } - - /** - * Split a {@code String} at the first occurrence of the delimiter. - * Does not include the delimiter in the result. - * - * @param toSplit the string to split - * @param delimiter to split the string up with - * - * @return a two element array with index 0 being before the delimiter, and - * index 1 being after the delimiter (neither element includes the delimiter); - * or {@code null} if the delimiter wasn't found in the given input {@code String} - */ - public static String[] split(String toSplit, String delimiter) { - if (!hasLength(toSplit) || !hasLength(delimiter)) { - return null; - } - int offset = toSplit.indexOf(delimiter); - if (offset < 0) { - return null; - } - - String beforeDelimiter = toSplit.substring(0, offset); - String afterDelimiter = toSplit.substring(offset + delimiter.length()); - return new String[]{beforeDelimiter, afterDelimiter}; - } - - /** - * Tokenize the given {@code String} into a {@code String} array via a - * {@link StringTokenizer}. - *

Trims tokens and omits empty tokens. - *

The given {@code delimiters} string can consist of any number of - * delimiter characters. Each of those characters can be used to separate - * tokens. A delimiter is always a single character; for multi-character - * delimiters, consider using {@link #delimitedListToStringArray}. - * - * @param str the {@code String} to tokenize - * @param delimiters the delimiter characters, assembled as a {@code String} - * (each of the characters is individually considered as a delimiter) - * - * @return an array of the tokens - * @see StringTokenizer - * @see String#trim() - * @see #delimitedListToStringArray - */ - public static String[] tokenizeToStringArray(String str, String delimiters) { - return tokenizeToStringArray(str, delimiters, true, true); - } - - /** - * Convert a comma delimited list (e.g., a row from a CSV file) into a set. - *

Note that this will suppress duplicates, and as of 4.2, the elements in - * the returned set will preserve the original order in a {@link LinkedHashSet}. - * - * @param str the input {@code String} - * - * @return a set of {@code String} entries in the list - * @see #removeDuplicateStrings(String[]) - */ - public static Set commaDelimitedListToSet(String str) { - Set set = new LinkedHashSet<>(); - String[] tokens = commaDelimitedListToStringArray(str); - for (String token : tokens) { - set.add(token); - } - return set; - } - - /** - * Convert a comma delimited list (e.g., a row from a CSV file) into an - * array of strings. - * - * @param str the input {@code String} - * - * @return an array of strings, or the empty array in case of empty input - */ - public static String[] commaDelimitedListToStringArray(String str) { - return delimitedListToStringArray(str, ","); - } - - /** - * Convert a {@code Collection} into a delimited {@code String} (e.g., CSV). - *

Useful for {@code toString()} implementations. - * - * @param coll the {@code Collection} to convert - * - * @return the delimited {@code String} - */ - public static String collectionToCommaDelimitedString(Collection coll) { - return collectionToDelimitedString(coll, ","); - } - - /** - * Convert a {@code String} array into a comma delimited {@code String} - * (i.e., CSV). - *

Useful for {@code toString()} implementations. - * - * @param arr the array to display - * - * @return the delimited {@code String} - */ - public static String arrayToCommaDelimitedString(Object[] arr) { - return arrayToDelimitedString(arr, ","); - } - - /** - * Convert a {@code String} array into a delimited {@code String} (e.g. CSV). - *

Useful for {@code toString()} implementations. - * - * @param arr the array to display - * @param delim the delimiter to use (typically a ",") - * - * @return the delimited {@code String} - */ - public static String arrayToDelimitedString(Object[] arr, String delim) { - if (ObjectUtils.isEmpty(arr)) { - return ""; - } - if (arr.length == 1) { - return ObjectUtils.nullSafeToString(arr[0]); - } - - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < arr.length; i++) { - if (i > 0) { - sb.append(delim); - } - sb.append(arr[i]); - } - return sb.toString(); - } - -} diff --git a/src/test/java/com/antkorwin/xsync/XMutexFactoryTest.java b/src/test/java/com/antkorwin/xsync/XMutexFactoryTest.java index 627edcd..55f00ac 100644 --- a/src/test/java/com/antkorwin/xsync/XMutexFactoryTest.java +++ b/src/test/java/com/antkorwin/xsync/XMutexFactoryTest.java @@ -2,9 +2,10 @@ import com.antkorwin.commonutils.concurrent.ConcurrentSet; import com.antkorwin.commonutils.gc.GcUtils; -import com.antkorwin.xsync.springframework.util.ConcurrentReferenceHashMap; + import org.assertj.core.api.Assertions; import org.junit.Test; +import org.springframework.util.ConcurrentReferenceHashMap; import java.util.*; import java.util.concurrent.TimeUnit; @@ -23,7 +24,7 @@ public class XMutexFactoryTest { private static final int TIMEOUT_FOR_PREVENTION_OF_DEADLOCK = 30000; - private static final int NUMBER_OF_MUTEXES = 100000; + private static final int NUMBER_OF_MUTEXES = 100_000; private static final int NUMBER_OF_ITERATIONS = NUMBER_OF_MUTEXES * 100; private static final String ID_STRING = "c117c526-606e-41b6-8197-1a6ba779f69b"; @@ -34,7 +35,7 @@ public void testGetSameMutexFromTwoDifferentInstanceOfEqualsKeys() { UUID firstId = UUID.fromString(ID_STRING); UUID secondId = UUID.fromString(ID_STRING); // Check precondition - Assertions.assertThat(firstId != secondId).isTrue(); + Assertions.assertThat(firstId).isNotSameAs(secondId); Assertions.assertThat(firstId).isEqualTo(secondId); // Act @@ -45,7 +46,7 @@ public void testGetSameMutexFromTwoDifferentInstanceOfEqualsKeys() { Assertions.assertThat(firstMutex).isNotNull(); Assertions.assertThat(secondMutex).isNotNull(); Assertions.assertThat(firstMutex).isEqualTo(secondMutex); - Assertions.assertThat(firstMutex == secondMutex).isTrue(); + Assertions.assertThat(firstMutex).isSameAs(secondMutex); } @Test @@ -134,7 +135,7 @@ public void testALotOfHashCodes() { // because the GC can delete a unused references: resultReferences.add(mutex); // Assert - Assertions.assertThat(mutex == firstMutex).isTrue(); + Assertions.assertThat(mutex).isSameAs(firstMutex); } // Assertions diff --git a/src/test/java/com/antkorwin/xsync/XMutexTest.java b/src/test/java/com/antkorwin/xsync/XMutexTest.java index 6a94b24..4e33c0b 100755 --- a/src/test/java/com/antkorwin/xsync/XMutexTest.java +++ b/src/test/java/com/antkorwin/xsync/XMutexTest.java @@ -24,8 +24,9 @@ public void testMutexEquals() { XMutex mutex2 = new XMutex<>(SECOND_KEY); // Act & Assert - Assertions.assertThat(FIRST_KEY != SECOND_KEY).isTrue(); + Assertions.assertThat(FIRST_KEY).isNotSameAs(SECOND_KEY); Assertions.assertThat(mutex1).isEqualTo(mutex2); + Assertions.assertThat(mutex1).isNotSameAs(mutex2); } @Test diff --git a/src/test/java/com/antkorwin/xsync/XSyncTest.java b/src/test/java/com/antkorwin/xsync/XSyncTest.java index f359b1e..5c08827 100644 --- a/src/test/java/com/antkorwin/xsync/XSyncTest.java +++ b/src/test/java/com/antkorwin/xsync/XSyncTest.java @@ -126,6 +126,15 @@ public void testEvaluateSupplier() { Assertions.assertThat(sum).isEqualTo(expectedSum); } + //TODO: +// @Test +// public void testWorkWithoutLockingWhenTwoInstanceSXync(){ +// XSync xsyncFirst = new XSync<>(); +// XSync xsyncSecond = new XSync<>(); +// +// xsyncFirst +// } + @Test(expected = IndexOutOfBoundsException.class) public void testThrowExceptionInFunction() throws Exception { // Arrange