Home | History | Annotate | Download | only in internal
      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