Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
de.unkrig.commons.lang.OptionalMethods Maven / Gradle / Ivy
/*
* de.unkrig.commons - A general-purpose Java class library
*
* Copyright (c) 2017, Arno Unkrig
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
* following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote
* products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package de.unkrig.commons.lang;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import de.unkrig.commons.nullanalysis.Nullable;
/**
* Helpers for convenient handling of methods and classes that may or may be loadable at runtime, e.g. those which
* appeared in a later JRE version.
*
* Example:
*
*
* The JRE class "{@code java.lang.Character}" has a method "{@code boolean isAlphabetic(int)}", but only since Java
* 7, so you cannot invoke this method from your code if you compile it with JDK 6 (so that it is still runnable
* in a JRE 6). To make the method available for your code, proceed as follows:
*
*
* class MyClass {
*
* // ...
*
* private static final MethodWrapper1
* CHARACTER__IS_ALPHABETIC = OptionalMethods.get1(
* Character.class, // declaringClass
* "isAlphabetic", // methodName
* int.class, // parameterType
* RuntimeException.class // checkedException
* );
*
* // ...
*
* public void m(int cp) {
*
* // Will throw an UnsupportedOperationException if this code runs in a JRE 6.
* if (CHARACTER__IS_ALPHABETIC.invoke(null, cp)) {
* // ...
* }
*
* // Alternatively, it is possible to check whether the wrapped method is available:
* if (CHARACTER__IS_ALPHABETIC.isAvailable() && CHARACTER__IS_ALPHABETIC.invoke(null, cp)) {
* // ...
* }
* }
* }
*
*
*/
public final
class OptionalMethods {
private OptionalMethods() {}
// ================================== METHOD WITH ZERO PARAMETERS ==================================
/**
* Wrapper for a method with zero parameters.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The exception that the method may throw (use {@code RuntimeException} iff the method does not
* declare any checked exceptions)
*/
public
interface MethodWrapper0 {
/**
* @return Whether the wrapped method exists
*/
boolean isAvailable();
/**
* Invokes the wrapped method, or, iff that method does not exist, takes an alternate action.
*
* @param target Ignored iff the wrapped method is STATIC
*/
@Nullable R
invoke(@Nullable DC target) throws EX;
}
/**
* Returns a wrapper for a zero-parameter method of the declaringClass, based on the
* methodName . If that method does not exist, a wrapper is returned that will throw an {@link
* UnsupportedOperationException} when invoked.
*
* @param The return type of the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper0
get0(
@Nullable String message,
@Nullable ClassLoader declaringClassLoader,
final String declaringClassName,
final String methodName
) {
return OptionalMethods.get0(
message,
declaringClassLoader,
declaringClassName,
methodName,
null // checkedException
);
}
/**
* Returns a wrapper for a zero-parameter method of the declaringClass, based on the
* methodName . If that method does not exist, a wrapper is returned that will throw an {@link
* UnsupportedOperationException} when invoked.
*
* @param The return type of the method
* @param The (single) checked exception declared for the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
@SuppressWarnings("unchecked") public static MethodWrapper0
get0(
@Nullable final String message,
@Nullable ClassLoader declaringClassLoader,
final String declaringClassName,
final String methodName,
@Nullable final Class checkedException
) {
if (declaringClassLoader == null) declaringClassLoader = Thread.currentThread().getContextClassLoader();
assert declaringClassLoader != null;
try {
return (MethodWrapper0) OptionalMethods.get0(
message,
declaringClassLoader.loadClass(declaringClassName),
methodName,
checkedException
);
} catch (ClassNotFoundException e) {
return OptionalMethods.missingMethod0(message, declaringClassName, methodName);
}
}
/**
* Returns a wrapper for a zero-parameter method of the declaringClass, based on the
* methodName . If that method does not exist, a wrapper is returned that will throw an {@link
* UnsupportedOperationException} when invoked.
*
* @param The class that declares the method
* @param The return type of the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper0
get0(@Nullable String message, Class declaringClass, final String methodName) {
return OptionalMethods.get0(
message,
declaringClass,
methodName,
null // checkedException
);
}
/**
* Returns a wrapper for a zero-parameter method of the declaringClass, based on the
* methodName . If that method does not exist, a wrapper is returned that will throw an {@link
* UnsupportedOperationException} when invoked.
*
* @param The class that declares the method
* @param The return type of the method
* @param The (single) checked exception declared for the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper0
get0(
@Nullable final String message,
final Class declaringClass,
final String methodName,
@Nullable final Class checkedException
) {
try {
final Method method = declaringClass.getMethod(methodName);
return new MethodWrapper0() {
@Override public boolean
isAvailable() { return true; }
@Override @Nullable public R
invoke(@Nullable DC target) throws EX {
return OptionalMethods.invoke(method, target, checkedException);
}
};
} catch (NoSuchMethodException e) {
return OptionalMethods.missingMethod0(message, declaringClass.getName(), methodName);
}
}
// ================================== METHOD WITH ONE PARAMETER ==================================
/**
* Wrapper for a method with one parameter.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The type of the single parameter of the wrapped method
* @param The exception that the method may throw (use {@code RuntimeException} iff the method does not
* declare any checked exceptions)
*/
public
interface MethodWrapper1 {
/**
* @return Whether the wrapped method exists
*/
boolean isAvailable();
/**
* Invokes the wrapped method, or, iff that method does not exist, takes an alternate action.
*
* @param target Ignored iff the wrapped method is STATIC
*/
@Nullable R
invoke(@Nullable DC target, @Nullable P argument) throws EX;
}
/**
* Returns a wrapper for a single-parameter method of the declaringClass, based on
* methodName and parameterType . If that method does not exist, a wrapper is returned that
* will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The return type of the wrapped method
* @param The type of the single parameter of the wrapped method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper1, R, P, RuntimeException>
get1(
@Nullable String message,
@Nullable ClassLoader declaringClassLoader,
String declaringClassName,
String methodName,
Class parameterType
) {
return OptionalMethods.get1(
message,
declaringClassLoader,
declaringClassName,
methodName,
parameterType,
null // checkedException
);
}
/**
* Returns a wrapper for a single-parameter method of the declaringClass, based on
* methodName and parameterType . If that method does not exist, a wrapper is returned that
* will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The return type of the wrapped method
* @param The type of the single parameter of the wrapped method
* @param The (single) checked exception declared for the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper1, R, P, EX>
get1(
@Nullable final String message,
@Nullable ClassLoader declaringClassLoader,
final String declaringClassName,
final String methodName,
final Class parameterType,
@Nullable final Class checkedException
) {
if (declaringClassLoader == null) declaringClassLoader = Thread.currentThread().getContextClassLoader();
assert declaringClassLoader != null;
try {
return OptionalMethods.get1(
message,
declaringClassLoader.loadClass(declaringClassName),
methodName,
parameterType,
checkedException
);
} catch (ClassNotFoundException cnfe) {
return OptionalMethods.missingMethod1(message, declaringClassName, methodName, parameterType);
}
}
/**
* Returns a wrapper for a single-parameter method of the declaringClass, based on
* methodName and parameterType . If that method does not exist, a wrapper is returned that
* will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The type of the single parameter of the wrapped method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper1
get1(@Nullable String message, Class declaringClass, String methodName, Class parameterType) {
return OptionalMethods.get1(
message,
declaringClass,
methodName,
parameterType,
null // checkedException
);
}
/**
* Returns a wrapper for a single-parameter method of the declaringClass, based on
* methodName and parameterType . If that method does not exist, a wrapper is returned that
* will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The type of the single parameter of the wrapped method
* @param The (single) checked exception declared for the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper1
get1(
@Nullable final String message,
final Class declaringClass,
final String methodName,
final Class parameterType,
@Nullable final Class checkedException
) {
try {
final Method method = declaringClass.getMethod(methodName, parameterType);
return new MethodWrapper1() {
@Override public boolean
isAvailable() { return true; }
@Override @Nullable public R
invoke(@Nullable DC target, @Nullable P argument) throws EX {
return OptionalMethods.invoke(method, target, checkedException, argument);
}
};
} catch (NoSuchMethodException e) {
return OptionalMethods.missingMethod1(
message,
declaringClass.getName(),
methodName,
parameterType
);
}
}
// ================================== METHOD WITH TWO PARAMETERS ==================================
/**
* Wrapper for a method with two parameters.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The type of first parameter of the wrapped method
* @param The type of second parameter of the wrapped method
* @param The exception that the method may throw (use {@code RuntimeException} iff the method does not
* declare any checked exceptions)
*/
public
interface MethodWrapper2 {
/**
* @return Whether the wrapped method exists
*/
boolean isAvailable();
/**
* Invokes the wrapped method, or, iff that method does not exist, takes an alternate action.
*
* @param target Ignored iff the wrapped method is STATIC
*/
@Nullable R
invoke(@Nullable DC target, @Nullable P1 argument1, @Nullable P2 argument2) throws EX;
}
/**
* Returns a wrapper for a two-parameter method of the declaringClass, based on
* methodName , parameterType1 and parameterType2 . If that method does not exist,
* a wrapper is returned that will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The return type of the wrapped method
* @param The type of the first parameter of the wrapped method
* @param The type of the second parameter of the wrapped method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper2, R, P1, P2, RuntimeException>
get2(
@Nullable String message,
@Nullable ClassLoader declaringClassLoader,
final String declaringClassName,
final String methodName,
Class parameterType1,
Class parameterType2
) {
return OptionalMethods.get2(
message,
declaringClassLoader,
declaringClassName,
methodName,
parameterType1,
parameterType2,
null // checkedException
);
}
/**
* Returns a wrapper for a two-parameter method of the declaringClass, based on
* methodName , parameterType1 and parameterType2 . If that method does not exist,
* a wrapper is returned that will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The return type of the wrapped method
* @param The type of the first parameter of the wrapped method
* @param The type of the second parameter of the wrapped method
* @param The (single) checked exception declared for the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper2, R, P1, P2, EX>
get2(
@Nullable final String message,
@Nullable ClassLoader declaringClassLoader,
final String declaringClassName,
final String methodName,
Class parameterType1,
Class parameterType2,
@Nullable final Class checkedException
) {
if (declaringClassLoader == null) declaringClassLoader = Thread.currentThread().getContextClassLoader();
assert declaringClassLoader != null;
try {
return OptionalMethods.get2(
message,
declaringClassLoader.loadClass(declaringClassName),
methodName,
parameterType1,
parameterType2,
checkedException
);
} catch (ClassNotFoundException e) {
return OptionalMethods.missingMethod2(
message,
declaringClassName,
methodName,
parameterType1,
parameterType2
);
}
}
/**
* Returns a wrapper for a two-parameter method of the declaringClass, based on
* methodName , parameterType1 and parameterType2 . If that method does not exist,
* a wrapper is returned that will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The type of the first parameter of the wrapped method
* @param The type of the second parameter of the wrapped method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper2
get2(
@Nullable final String message,
final Class declaringClass,
final String methodName,
Class parameterType1,
Class parameterType2
) {
return OptionalMethods.get2(
message,
declaringClass,
methodName,
parameterType1,
parameterType2,
null // checkedException
);
}
/**
* Returns a wrapper for a two-parameter method of the declaringClass, based on
* methodName , parameterType1 and parameterType2 . If that method does not exist,
* a wrapper is returned that will throw an {@link UnsupportedOperationException} when invoked.
*
* @param The class that declares the wrapped method
* @param The return type of the wrapped method
* @param The type of the first parameter of the wrapped method
* @param The type of the second parameter of the wrapped method
* @param The (single) checked exception declared for the method
* @param message The message for the {@link UnsupportedOperationException}; if {@code null}, then the method
* signature is used
*/
public static MethodWrapper2
get2(
@Nullable final String message,
final Class declaringClass,
final String methodName,
Class parameterType1,
Class parameterType2,
@Nullable final Class checkedException
) {
Method method;
try {
method = declaringClass.getMethod(methodName, parameterType1, parameterType2);
} catch (NoSuchMethodException nsme) {
try {
method = declaringClass.getDeclaredMethod(methodName, parameterType1, parameterType2);
if (!Modifier.isPublic(method.getModifiers())) method.setAccessible(true);
} catch (NoSuchMethodException nsme2) {
return OptionalMethods.missingMethod2(
message,
declaringClass.getName(),
methodName,
parameterType1,
parameterType2
);
}
}
final Method finalMethod = method;
return new MethodWrapper2() {
@Override public boolean
isAvailable() { return true; }
@Override @Nullable public R
invoke(@Nullable DC target, @Nullable P1 argument1, @Nullable P2 argument2) throws EX {
return OptionalMethods.invoke(finalMethod, target, checkedException, argument1, argument2);
}
};
}
private static MethodWrapper0
missingMethod0(
@Nullable final String explicitMessage,
final String declaringClassName,
final String methodName
) {
return new MethodWrapper0() {
@Override public boolean
isAvailable() { return false; }
@Override @Nullable public R
invoke(@Nullable Object target) {
throw new UnsupportedOperationException(
OptionalMethods.cookMessage(explicitMessage, declaringClassName, methodName)
);
}
};
}
private static MethodWrapper1
missingMethod1(
@Nullable final String explicitMessage,
final String declaringClassName,
final String methodName,
final Class parameterType
) {
return new MethodWrapper1() {
@Override public boolean
isAvailable() { return false; }
@Override @Nullable public R
invoke(@Nullable DC target, @Nullable P argument) {
throw new UnsupportedOperationException(
OptionalMethods.cookMessage(explicitMessage, declaringClassName, methodName, parameterType)
);
}
};
}
private static MethodWrapper2
missingMethod2(
@Nullable final String explicitMessage,
final String declaringClassName,
final String methodName,
final Class parameterType1,
final Class parameterType2
) {
return new MethodWrapper2() {
@Override public boolean
isAvailable() { return false; }
@Override @Nullable public R
invoke(@Nullable DC target, @Nullable P1 argument1, @Nullable P2 argument2) {
throw new UnsupportedOperationException(
OptionalMethods.cookMessage(
explicitMessage,
declaringClassName,
methodName,
parameterType1,
parameterType2
)
);
}
};
}
private static String
cookMessage(
@Nullable String explicitMessage,
String declaringClassName,
String methodName,
Class>... parameterTypes
) {
if (explicitMessage != null) return explicitMessage;
StringBuilder sb = new StringBuilder();
sb.append(declaringClassName).append('.').append(methodName).append('(');
if (parameterTypes.length >= 1) {
sb.append(parameterTypes[0]);
for (int i = 1; i < parameterTypes.length; i++) sb.append(", ").append(parameterTypes[i]);
}
return sb.append(')').toString();
}
@SuppressWarnings("unchecked") private static R
invoke(final Method method, @Nullable DC target, @Nullable Class checkedException, Object... arguments)
throws EX {
try {
return (R) method.invoke(target, arguments);
} catch (InvocationTargetException e) {
Throwable te = e.getTargetException();
if (te instanceof RuntimeException) throw (RuntimeException) te;
if (te instanceof Error) throw (Error) te;
assert checkedException != null : "Caught undeclared checked exception " + te;
assert checkedException.isAssignableFrom(te.getClass());
throw (EX) te;
} catch (Exception e) {
throw new AssertionError(e);
}
}
}