18cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller/*
28cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  Licensed to the Apache Software Foundation (ASF) under one or more
38cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  contributor license agreements.  See the NOTICE file distributed with
48cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  this work for additional information regarding copyright ownership.
58cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  The ASF licenses this file to You under the Apache License, Version 2.0
68cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  (the "License"); you may not use this file except in compliance with
78cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  the License.  You may obtain a copy of the License at
88cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *
98cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *     http://www.apache.org/licenses/LICENSE-2.0
108cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *
118cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  Unless required by applicable law or agreed to in writing, software
128cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  distributed under the License is distributed on an "AS IS" BASIS,
138cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
148cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  See the License for the specific language governing permissions and
158cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *  limitations under the License.
168cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller */
178cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
188cdfc1007818eec034fd6f547425cfda7369ed49Neil Fullerpackage com.squareup.okhttp.internal;
198cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
208cdfc1007818eec034fd6f547425cfda7369ed49Neil Fullerimport java.lang.reflect.InvocationTargetException;
218cdfc1007818eec034fd6f547425cfda7369ed49Neil Fullerimport java.lang.reflect.Method;
228cdfc1007818eec034fd6f547425cfda7369ed49Neil Fullerimport java.lang.reflect.Modifier;
238cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
248cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller/**
258cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller * Duck-typing for methods: Represents a method that may or may not be present on an object.
268cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller *
278cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller * @param <T> the type of the object the method might be on, typically an interface or base class
288cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller */
298cdfc1007818eec034fd6f547425cfda7369ed49Neil Fullerclass OptionalMethod<T> {
308cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
318cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /** The return type of the method. null means "don't care". */
328cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  private final Class<?> returnType;
338cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
348cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  private final String methodName;
358cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
368cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  private final Class[] methodParams;
378cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
388cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
398cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Creates an optional method.
408cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   *
418cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @param returnType the return type to required, null if it does not matter
428cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @param methodName the name of the method
438cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @param methodParams the method parameter types
448cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
458cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  public OptionalMethod(Class<?> returnType, String methodName, Class... methodParams) {
468cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    this.returnType = returnType;
478cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    this.methodName = methodName;
488cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    this.methodParams = methodParams;
498cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
508cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
518cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
528cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Returns true if the method exists on the supplied {@code target}.
538cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
548cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  public boolean isSupported(T target) {
558cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    return getMethod(target.getClass()) != null;
568cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
578cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
588cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
598cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Invokes the method on {@code target} with {@code args}. If the method does not exist or is not
608cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * public then {@code null} is returned. See also
618cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * {@link #invokeOptionalWithoutCheckedException(Object, Object...)}.
628cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   *
638cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @throws IllegalArgumentException if the arguments are invalid
648cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @throws InvocationTargetException if the invocation throws an exception
658cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
668cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  public Object invokeOptional(T target, Object... args) throws InvocationTargetException {
678cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    Method m = getMethod(target.getClass());
688cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    if (m == null) {
698cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      return null;
708cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
718cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    try {
728cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      return m.invoke(target, args);
738cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    } catch (IllegalAccessException e) {
748cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      return null;
758cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
768cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
778cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
788cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
798cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Invokes the method on {@code target}.  If the method does not exist or is not
808cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * public then {@code null} is returned. Any RuntimeException thrown by the method is thrown,
818cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * checked exceptions are wrapped in an {@link AssertionError}.
828cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   *
838cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @throws IllegalArgumentException if the arguments are invalid
848cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
858cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  public Object invokeOptionalWithoutCheckedException(T target, Object... args) {
868cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    try {
878cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      return invokeOptional(target, args);
888cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    } catch (InvocationTargetException e) {
898cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      Throwable targetException = e.getTargetException();
908cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      if (targetException instanceof RuntimeException) {
918cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller        throw (RuntimeException) targetException;
928cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      }
938cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      throw new AssertionError("Unexpected exception", targetException);
948cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
958cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
968cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
978cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
988cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Invokes the method on {@code target} with {@code args}. Throws an error if the method is not
998cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * supported. See also {@link #invokeWithoutCheckedException(Object, Object...)}.
1008cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   *
1018cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @throws IllegalArgumentException if the arguments are invalid
1028cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @throws InvocationTargetException if the invocation throws an exception
1038cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
1048cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  public Object invoke(T target, Object... args) throws InvocationTargetException {
1058cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    Method m = getMethod(target.getClass());
1068cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    if (m == null) {
1078cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      throw new AssertionError("Method " + methodName + " not supported for object " + target);
1088cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
1098cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    try {
1108cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      return m.invoke(target, args);
1118cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    } catch (IllegalAccessException e) {
1128cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      // Method should be public: we checked.
1138cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      throw new AssertionError("Unexpectedly could not call: " + m, e);
1148cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
1158cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
1168cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
1178cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
1188cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Invokes the method on {@code target}. Throws an error if the method is not supported. Any
1198cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * RuntimeException thrown by the method is thrown, checked exceptions are wrapped in
1208cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * an {@link AssertionError}.
1218cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   *
1228cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * @throws IllegalArgumentException if the arguments are invalid
1238cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
1248cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  public Object invokeWithoutCheckedException(T target, Object... args) {
1258cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    try {
1268cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      return invoke(target, args);
1278cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    } catch (InvocationTargetException e) {
1288cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      Throwable targetException = e.getTargetException();
1298cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      if (targetException instanceof RuntimeException) {
1308cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller        throw (RuntimeException) targetException;
1318cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      }
1328cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      throw new AssertionError("Unexpected exception", targetException);
1338cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
1348cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
1358cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
1368cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  /**
1378cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * Perform a lookup for the method. No caching.
1388cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * In order to return a method the method name and arguments must match those specified when
1398cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * the {@link OptionalMethod} was created. If the return type is specified (i.e. non-null) it
1408cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   * must also be compatible. The method must also be public.
1418cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller   */
1428cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  private Method getMethod(Class<?> clazz) {
1438cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    Method method = null;
1448cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    if (methodName != null) {
1458cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      method = getPublicMethod(clazz, methodName, methodParams);
1468cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      if (method != null
1478cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller          && returnType != null
1488cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller          && !returnType.isAssignableFrom(method.getReturnType())) {
1498cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
1508cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller        // If the return type is non-null it must be compatible.
1518cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller        method = null;
1528cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      }
1538cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
1548cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    return method;
1558cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
1568cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller
1578cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  private static Method getPublicMethod(Class<?> clazz, String methodName, Class[] parameterTypes) {
1588cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    Method method = null;
1598cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    try {
1608cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      method = clazz.getMethod(methodName, parameterTypes);
1618cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
1628cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller        method = null;
1638cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      }
1648cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    } catch (NoSuchMethodException e) {
1658cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller      // None.
1668cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    }
1678cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller    return method;
1688cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller  }
1698cdfc1007818eec034fd6f547425cfda7369ed49Neil Fuller}
170