1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.squareup.okhttp.internal; 19 20 import java.lang.reflect.InvocationTargetException; 21 import java.lang.reflect.Method; 22 import java.lang.reflect.Modifier; 23 24 /** 25 * Duck-typing for methods: Represents a method that may or may not be present on an object. 26 * 27 * @param <T> the type of the object the method might be on, typically an interface or base class 28 */ 29 class OptionalMethod<T> { 30 31 /** The return type of the method. null means "don't care". */ 32 private final Class<?> returnType; 33 34 private final String methodName; 35 36 private final Class[] methodParams; 37 38 /** 39 * Creates an optional method. 40 * 41 * @param returnType the return type to required, null if it does not matter 42 * @param methodName the name of the method 43 * @param methodParams the method parameter types 44 */ 45 public OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) { 46 this.returnType = returnType; 47 this.methodName = methodName; 48 this.methodParams = methodParams; 49 } 50 51 /** 52 * Returns true if the method exists on the supplied {@code target}. 53 */ 54 public boolean isSupported(T target) { 55 return getMethod(target.getClass()) != null; 56 } 57 58 /** 59 * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not 60 * public then {@code null} is returned. See also 61 * {@link #invokeOptionalWithoutCheckedException(Object, Object...)}. 62 * 63 * @throws IllegalArgumentException if the arguments are invalid 64 * @throws InvocationTargetException if the invocation throws an exception 65 */ 66 public Object invokeOptional(T target, Object... args) throws InvocationTargetException { 67 Method m = getMethod(target.getClass()); 68 if (m == null) { 69 return null; 70 } 71 try { 72 return m.invoke(target, args); 73 } catch (IllegalAccessException e) { 74 return null; 75 } 76 } 77 78 /** 79 * Invokes the method on {@code target}. If the method does not exist or is not 80 * public then {@code null} is returned. Any RuntimeException thrown by the method is thrown, 81 * checked exceptions are wrapped in an {@link AssertionError}. 82 * 83 * @throws IllegalArgumentException if the arguments are invalid 84 */ 85 public Object invokeOptionalWithoutCheckedException(T target, Object... args) { 86 try { 87 return invokeOptional(target, args); 88 } catch (InvocationTargetException e) { 89 Throwable targetException = e.getTargetException(); 90 if (targetException instanceof RuntimeException) { 91 throw (RuntimeException) targetException; 92 } 93 throw new AssertionError("Unexpected exception", targetException); 94 } 95 } 96 97 /** 98 * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not 99 * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}. 100 * 101 * @throws IllegalArgumentException if the arguments are invalid 102 * @throws InvocationTargetException if the invocation throws an exception 103 */ 104 public Object invoke(T target, Object... args) throws InvocationTargetException { 105 Method m = getMethod(target.getClass()); 106 if (m == null) { 107 throw new AssertionError("Method " + methodName + " not supported for object " + target); 108 } 109 try { 110 return m.invoke(target, args); 111 } catch (IllegalAccessException e) { 112 // Method should be public: we checked. 113 throw new AssertionError("Unexpectedly could not call: " + m, e); 114 } 115 } 116 117 /** 118 * Invokes the method on {@code target}. Throws an error if the method is not supported. Any 119 * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in 120 * an {@link AssertionError}. 121 * 122 * @throws IllegalArgumentException if the arguments are invalid 123 */ 124 public Object invokeWithoutCheckedException(T target, Object... args) { 125 try { 126 return invoke(target, args); 127 } catch (InvocationTargetException e) { 128 Throwable targetException = e.getTargetException(); 129 if (targetException instanceof RuntimeException) { 130 throw (RuntimeException) targetException; 131 } 132 throw new AssertionError("Unexpected exception", targetException); 133 } 134 } 135 136 /** 137 * Perform a lookup for the method. No caching. 138 * In order to return a method the method name and arguments must match those specified when 139 * the {@link OptionalMethod} was created. If the return type is specified (i.e. non-null) it 140 * must also be compatible. The method must also be public. 141 */ 142 private Method getMethod(Class<?> clazz) { 143 Method method = null; 144 if (methodName != null) { 145 method = getPublicMethod(clazz, methodName, methodParams); 146 if (method != null 147 && returnType != null 148 && !returnType.isAssignableFrom(method.getReturnType())) { 149 150 // If the return type is non-null it must be compatible. 151 method = null; 152 } 153 } 154 return method; 155 } 156 157 private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) { 158 Method method = null; 159 try { 160 method = clazz.getMethod(methodName, parameterTypes); 161 if ((method.getModifiers() & Modifier.PUBLIC) == 0) { 162 method = null; 163 } 164 } catch (NoSuchMethodException e) { 165 // None. 166 } 167 return method; 168 } 169 } 170