io.grpc.okhttp.internal.OptionalMethod Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* Forked from OkHttp 2.5.0
*/
package io.grpc.okhttp.internal;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* Duck-typing for methods: Represents a method that may or may not be present on an object.
*
* @param the type of the object the method might be on, typically an interface or base class
*/
public class OptionalMethod {
/** The return type of the method. null means "don't care". */
private final Class> returnType;
private final String methodName;
@SuppressWarnings("rawtypes")
private final Class[] methodParams;
/**
* Creates an optional method.
*
* @param returnType the return type to required, null if it does not matter
* @param methodName the name of the method
* @param methodParams the method parameter types
*/
@SuppressWarnings("rawtypes")
public OptionalMethod(Class> returnType, String methodName, Class... methodParams) {
this.returnType = returnType;
this.methodName = methodName;
this.methodParams = methodParams;
}
/**
* Returns true if the method exists on the supplied {@code target}.
*/
public boolean isSupported(T target) {
return getMethod(target.getClass()) != null;
}
/**
* Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
* public then {@code null} is returned. See also
* {@link #invokeOptionalWithoutCheckedException(Object, Object...)}.
*
* @throws IllegalArgumentException if the arguments are invalid
* @throws InvocationTargetException if the invocation throws an exception
*/
public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
Method m = getMethod(target.getClass());
if (m == null) {
return null;
}
try {
return m.invoke(target, args);
} catch (IllegalAccessException e) {
return null;
}
}
/**
* Invokes the method on {@code target}. If the method does not exist or is not
* public then {@code null} is returned. Any RuntimeException thrown by the method is thrown,
* checked exceptions are wrapped in an {@link AssertionError}.
*
* @throws IllegalArgumentException if the arguments are invalid
*/
public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
try {
return invokeOptional(target, args);
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
AssertionError error = new AssertionError("Unexpected exception");
error.initCause(targetException);
throw error;
}
}
/**
* Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
* supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
*
* @throws IllegalArgumentException if the arguments are invalid
* @throws InvocationTargetException if the invocation throws an exception
*/
public Object invoke(T target, Object... args) throws InvocationTargetException {
Method m = getMethod(target.getClass());
if (m == null) {
throw new AssertionError("Method " + methodName + " not supported for object " + target);
}
try {
return m.invoke(target, args);
} catch (IllegalAccessException e) {
// Method should be public: we checked.
AssertionError error = new AssertionError("Unexpectedly could not call: " + m);
error.initCause(e);
throw error;
}
}
/**
* Invokes the method on {@code target}. Throws an error if the method is not supported. Any
* RuntimeException thrown by the method is thrown, checked exceptions are wrapped in
* an {@link AssertionError}.
*
* @throws IllegalArgumentException if the arguments are invalid
*/
public Object invokeWithoutCheckedException(T target, Object... args) {
try {
return invoke(target, args);
} catch (InvocationTargetException e) {
Throwable targetException = e.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
AssertionError error = new AssertionError("Unexpected exception");
error.initCause(targetException);
throw error;
}
}
/**
* Perform a lookup for the method. No caching.
* In order to return a method the method name and arguments must match those specified when
* the {@link OptionalMethod} was created. If the return type is specified (i.e. non-null) it
* must also be compatible. The method must also be public.
*/
private Method getMethod(Class> clazz) {
Method method = null;
if (methodName != null) {
method = getPublicMethod(clazz, methodName, methodParams);
if (method != null
&& returnType != null
&& !returnType.isAssignableFrom(method.getReturnType())) {
// If the return type is non-null it must be compatible.
method = null;
}
}
return method;
}
@SuppressWarnings("rawtypes")
private static Method getPublicMethod(Class> clazz, String methodName, Class[] parameterTypes) {
Method method = null;
try {
if (clazz == null) {
return null;
}
if ((clazz.getModifiers() & Modifier.PUBLIC) == 0) {
return getPublicMethod(clazz.getSuperclass(), methodName, parameterTypes);
}
method = clazz.getMethod(methodName, parameterTypes);
if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
method = null;
}
} catch (NoSuchMethodException e) {
// None.
}
return method;
}
}