1
2
3
4
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
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 }