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.faking;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.util.ArrayList;
12  import java.util.IdentityHashMap;
13  import java.util.Iterator;
14  import java.util.LinkedHashSet;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.Map.Entry;
18  import java.util.Set;
19  import java.util.regex.Pattern;
20  
21  import mockit.internal.util.ClassLoad;
22  
23  import org.checkerframework.checker.index.qual.NonNegative;
24  
25  /**
26   * Holds state associated with fake class containing {@linkplain mockit.Mock annotated fakes}.
27   */
28  public final class FakeStates {
29      private static final Pattern SPACE = Pattern.compile(" ");
30  
31      /**
32       * For each fake instance and each <code>@Mock</code> method containing the <code>Invocation</code> parameter, a
33       * runtime state will be kept here.
34       */
35      @NonNull
36      private final Map<Object, List<FakeState>> fakesToFakeStates;
37      @NonNull
38      private final Map<Object, List<FakeState>> startupFakesToFakeStates;
39      @NonNull
40      private final Set<FakeState> fakeStatesWithExpectations;
41  
42      public FakeStates() {
43          startupFakesToFakeStates = new IdentityHashMap<>(2);
44          fakesToFakeStates = new IdentityHashMap<>(8);
45          fakeStatesWithExpectations = new LinkedHashSet<>(8);
46      }
47  
48      void addStartupFakeAndItsFakeStates(@NonNull Object fake, @NonNull List<FakeState> fakeStates) {
49          startupFakesToFakeStates.put(fake, fakeStates);
50          registerExpectations(fakeStates);
51      }
52  
53      void addFakeAndItsFakeStates(@NonNull Object fake, @NonNull List<FakeState> fakeStates) {
54          fakesToFakeStates.put(fake, fakeStates);
55          registerExpectations(fakeStates);
56      }
57  
58      public void copyFakeStates(@NonNull Object previousFake, @NonNull Object newFake) {
59          List<FakeState> fakeStates = fakesToFakeStates.get(previousFake);
60  
61          if (fakeStates != null) {
62              List<FakeState> copiedFakeStates = new ArrayList<>(fakeStates.size());
63  
64              for (FakeState fakeState : fakeStates) {
65                  copiedFakeStates.add(new FakeState(fakeState));
66              }
67  
68              fakesToFakeStates.put(newFake, copiedFakeStates);
69              registerExpectations(copiedFakeStates);
70          }
71      }
72  
73      public void removeClassState(@NonNull Class<?> redefinedClass,
74              @Nullable String internalNameForOneOrMoreFakeClasses) {
75          removeFakeStates(redefinedClass);
76  
77          if (internalNameForOneOrMoreFakeClasses != null) {
78              if (internalNameForOneOrMoreFakeClasses.indexOf(' ') < 0) {
79                  removeFakeStates(internalNameForOneOrMoreFakeClasses);
80              } else {
81                  String[] fakeClassesInternalNames = SPACE.split(internalNameForOneOrMoreFakeClasses);
82  
83                  for (String fakeClassInternalName : fakeClassesInternalNames) {
84                      removeFakeStates(fakeClassInternalName);
85                  }
86              }
87          }
88      }
89  
90      private void removeFakeStates(@NonNull Class<?> redefinedClass) {
91          Iterator<List<FakeState>> itr = fakesToFakeStates.values().iterator();
92  
93          while (itr.hasNext()) {
94              List<FakeState> fakeStates = itr.next();
95              FakeState fakeState = fakeStates.get(0);
96  
97              if (fakeState.getRealClass() == redefinedClass) {
98                  fakeStates.forEach(fakeStatesWithExpectations::remove);
99                  fakeStates.clear();
100                 itr.remove();
101             }
102         }
103     }
104 
105     private void removeFakeStates(@NonNull String fakeClassInternalName) {
106         Class<?> fakeClass = ClassLoad.loadClass(fakeClassInternalName.replace('/', '.'));
107         Iterator<Entry<Object, List<FakeState>>> itr = fakesToFakeStates.entrySet().iterator();
108 
109         while (itr.hasNext()) {
110             Entry<Object, List<FakeState>> fakeAndFakeStates = itr.next();
111             Object fake = fakeAndFakeStates.getKey();
112 
113             if (fake.getClass() == fakeClass) {
114                 fakeAndFakeStates.getValue().forEach(fakeStatesWithExpectations::remove);
115                 itr.remove();
116             }
117         }
118     }
119 
120     public boolean updateFakeState(@NonNull Object fake, @NonNegative int fakeStateIndex) {
121         FakeState fakeState = getFakeState(fake, fakeStateIndex);
122         return fakeState.update();
123     }
124 
125     @NonNull
126     FakeState getFakeState(@NonNull Object fake, @NonNegative int fakeStateIndex) {
127         List<FakeState> fakeStates = startupFakesToFakeStates.get(fake);
128 
129         if (fakeStates == null) {
130             fakeStates = fakesToFakeStates.get(fake);
131         }
132 
133         FakeState fakeState = fakeStates.get(fakeStateIndex);
134         assert fakeState != null;
135         return fakeState;
136     }
137 
138     public void verifyMissingInvocations() {
139         for (FakeState fakeState : fakeStatesWithExpectations) {
140             fakeState.verifyMissingInvocations();
141         }
142     }
143 
144     public void resetExpectations() {
145         for (FakeState fakeState : fakeStatesWithExpectations) {
146             fakeState.reset();
147         }
148     }
149 
150     private void registerExpectations(@NonNull List<FakeState> fakeStates) {
151         for (FakeState fakeState : fakeStates) {
152             if (fakeState.isWithExpectations()) {
153                 fakeStatesWithExpectations.add(fakeState);
154             }
155         }
156     }
157 }