View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.internal.expectations;
6   
7   import edu.umd.cs.findbugs.annotations.NonNull;
8   import edu.umd.cs.findbugs.annotations.Nullable;
9   
10  import java.util.IdentityHashMap;
11  import java.util.Map;
12  import java.util.Map.Entry;
13  
14  import mockit.internal.expectations.invocation.ExpectedInvocation;
15  
16  final class EquivalentInstances {
17      @NonNull
18      final Map<Object, Object> instanceMap;
19      @NonNull
20      final Map<Object, Object> replacementMap;
21  
22      EquivalentInstances() {
23          instanceMap = new IdentityHashMap<>();
24          replacementMap = new IdentityHashMap<>();
25      }
26  
27      void registerReplacementInstanceIfApplicable(@Nullable Object mock, @NonNull ExpectedInvocation invocation) {
28          Object replacementInstance = invocation.replacementInstance;
29  
30          if (replacementInstance != null && replacementInstance != invocation.instance) {
31              replacementMap.put(mock, replacementInstance);
32          }
33      }
34  
35      boolean isEquivalentInstance(@NonNull Object invocationInstance, @NonNull Object invokedInstance) {
36          return invocationInstance == invokedInstance || invocationInstance == replacementMap.get(invokedInstance)
37                  || invocationInstance == instanceMap.get(invokedInstance)
38                  || invokedInstance == instanceMap.get(invocationInstance);
39      }
40  
41      boolean areNonEquivalentInstances(@NonNull Object invocationInstance, @NonNull Object invokedInstance) {
42          boolean recordedInstanceMatchingAnyInstance = !isMatchingInstance(invocationInstance);
43          boolean invokedInstanceMatchingSpecificInstance = isMatchingInstance(invokedInstance);
44          return recordedInstanceMatchingAnyInstance && invokedInstanceMatchingSpecificInstance;
45      }
46  
47      private boolean isMatchingInstance(@NonNull Object instance) {
48          return instanceMap.containsKey(instance) || instanceMap.containsValue(instance)
49                  || replacementMap.containsKey(instance) || replacementMap.containsValue(instance);
50      }
51  
52      boolean areMatchingInstances(boolean matchInstance, @NonNull Object mock1, @NonNull Object mock2) {
53          if (matchInstance) {
54              return isEquivalentInstance(mock1, mock2);
55          }
56  
57          return !areInDifferentEquivalenceSets(mock1, mock2);
58      }
59  
60      private boolean areInDifferentEquivalenceSets(@NonNull Object mock1, @NonNull Object mock2) {
61          if (mock1 == mock2 || instanceMap.isEmpty()) {
62              return false;
63          }
64  
65          Object mock1Equivalent = instanceMap.get(mock1);
66          Object mock2Equivalent = instanceMap.get(mock2);
67  
68          if (mock1Equivalent == mock2 || mock2Equivalent == mock1) {
69              return false;
70          }
71  
72          // noinspection SimplifiableIfStatement
73          if (mock1Equivalent != null && mock2Equivalent != null) {
74              return true;
75          }
76  
77          return instanceMapHasMocksInSeparateEntries(mock1, mock2);
78      }
79  
80      private boolean instanceMapHasMocksInSeparateEntries(@NonNull Object mock1, @NonNull Object mock2) {
81          boolean found1 = false;
82          boolean found2 = false;
83  
84          for (Entry<Object, Object> entry : instanceMap.entrySet()) {
85              if (!found1 && isInMapEntry(entry, mock1)) {
86                  found1 = true;
87              }
88  
89              if (!found2 && isInMapEntry(entry, mock2)) {
90                  found2 = true;
91              }
92  
93              if (found1 && found2) {
94                  return true;
95              }
96          }
97  
98          return false;
99      }
100 
101     private static boolean isInMapEntry(@NonNull Entry<Object, Object> mapEntry, @NonNull Object mock) {
102         return mapEntry.getKey() == mock || mapEntry.getValue() == mock;
103     }
104 
105     @Nullable
106     Object getReplacementInstanceForMethodInvocation(@NonNull Object invokedInstance,
107             @NonNull String methodNameAndDesc) {
108         return methodNameAndDesc.charAt(0) == '<' ? null : replacementMap.get(invokedInstance);
109     }
110 
111     boolean isReplacementInstance(@NonNull Object invokedInstance, @NonNull String methodNameAndDesc) {
112         return methodNameAndDesc.charAt(0) != '<'
113                 && (replacementMap.containsKey(invokedInstance) || replacementMap.containsValue(invokedInstance));
114     }
115 }