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