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