Home | History | Annotate | Download | only in inline
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.dx.mockito.inline;
     18 
     19 import java.lang.reflect.InvocationTargetException;
     20 import java.lang.reflect.Method;
     21 import java.util.concurrent.Callable;
     22 import java.util.concurrent.ConcurrentHashMap;
     23 import java.util.concurrent.ConcurrentMap;
     24 
     25 /**
     26  * Called by method entry hooks. Dispatches these hooks to the {@code MockMethodAdvice}.
     27  */
     28 @SuppressWarnings("unused")
     29 public class MockMethodDispatcher {
     30     // An instance of {@code MockMethodAdvice}
     31     private Object mAdvice;
     32 
     33     // All dispatchers for various identifiers
     34     private static final ConcurrentMap<String, MockMethodDispatcher> INSTANCE =
     35             new ConcurrentHashMap<>();
     36 
     37     /**
     38      * Get the dispatcher for a identifier.
     39      *
     40      * @param identifier identifier of the dispatcher
     41      * @param instance instance that might be mocked
     42      *
     43      * @return dispatcher for the identifier
     44      */
     45     public static MockMethodDispatcher get(String identifier, Object instance) {
     46         if (instance == INSTANCE) {
     47             // Avoid endless loop if ConcurrentHashMap was redefined to check for being a mock.
     48             return null;
     49         } else {
     50             return INSTANCE.get(identifier);
     51         }
     52     }
     53 
     54     /**
     55      * Create a new dispatcher.
     56      *
     57      * @param advice Advice the dispatcher should call
     58      */
     59     private MockMethodDispatcher(Object advice) {
     60         mAdvice = advice;
     61     }
     62 
     63     /**
     64      * Set up a new advice to receive calls for an identifier
     65      *
     66      * @param identifier a unique identifier
     67      * @param advice advice the dispatcher should call
     68      */
     69     public static void set(String identifier, Object advice) {
     70         INSTANCE.putIfAbsent(identifier, new MockMethodDispatcher(advice));
     71     }
     72 
     73     /**
     74      * Calls {@code MockMethodAdvice#handle}
     75      */
     76     public Callable<?> handle(Object instance, Method origin, Object[] arguments) throws Throwable {
     77         try {
     78             return (Callable<?>) mAdvice.getClass().getMethod("handle", Object.class, Method.class,
     79                     Object[].class).invoke(mAdvice, instance, origin, arguments);
     80         } catch (InvocationTargetException e) {
     81             throw e.getCause();
     82         }
     83     }
     84 
     85     /**
     86      * Calls {@code MockMethodAdvice#isMock}
     87      */
     88     public boolean isMock(Object instance) {
     89         try {
     90             return (Boolean) mAdvice.getClass().getMethod("isMock", Object.class).invoke(mAdvice,
     91                     instance);
     92         } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
     93             throw new IllegalStateException(e);
     94         }
     95     }
     96 
     97     /**
     98      * Calls {@code MockMethodAdvice#isMocked}
     99      */
    100     public boolean isMocked(Object instance) {
    101         try {
    102             return (Boolean) mAdvice.getClass().getMethod("isMocked", Object.class).invoke(mAdvice,
    103                     instance);
    104         } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    105             throw new IllegalStateException(e);
    106         }
    107     }
    108 
    109     /**
    110      * Calls {@code MockMethodAdvice#isOverridden}
    111      */
    112     public boolean isOverridden(Object instance, Method origin) {
    113         try {
    114             return (Boolean) mAdvice.getClass().getMethod("isOverridden", Object.class,
    115                     Method.class).invoke(mAdvice, instance, origin);
    116         } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    117             throw new IllegalStateException(e);
    118         }
    119     }
    120 
    121     /**
    122      * Calls {@code MockMethodAdvice#getOrigin}
    123      */
    124     public Method getOrigin(Object mock, String instrumentedMethodWithTypeAndSignature)
    125             throws Throwable {
    126         return (Method) mAdvice.getClass().getMethod("getOrigin", Object.class,
    127                 String.class).invoke(mAdvice, mock, instrumentedMethodWithTypeAndSignature);
    128     }
    129 }
    130