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.invocation;
7   
8   import static mockit.internal.util.Utilities.JAVA8;
9   
10  import edu.umd.cs.findbugs.annotations.NonNull;
11  import edu.umd.cs.findbugs.annotations.Nullable;
12  
13  import java.lang.reflect.Array;
14  import java.util.ArrayList;
15  import java.util.Collection;
16  import java.util.Iterator;
17  import java.util.LinkedHashMap;
18  import java.util.LinkedHashSet;
19  import java.util.List;
20  import java.util.ListIterator;
21  import java.util.Map;
22  import java.util.Set;
23  import java.util.SortedMap;
24  import java.util.SortedSet;
25  import java.util.TreeMap;
26  import java.util.TreeSet;
27  import java.util.stream.Stream;
28  
29  import org.checkerframework.checker.index.qual.NonNegative;
30  
31  final class MultiValuedConversion {
32      @NonNull
33      private final InvocationResults invocationResults;
34      @NonNull
35      private final Class<?> returnType;
36      @NonNull
37      private final Object valueToReturn;
38  
39      MultiValuedConversion(@NonNull InvocationResults invocationResults, @NonNull Class<?> returnType,
40              @NonNull Object valueToReturn) {
41          this.invocationResults = invocationResults;
42          this.returnType = returnType;
43          this.valueToReturn = valueToReturn;
44      }
45  
46      void addMultiValuedResultBasedOnTheReturnType(boolean valueIsArray) {
47          if (returnType == void.class) {
48              addMultiValuedResult(valueIsArray);
49          } else if (returnType == Object.class) {
50              invocationResults.addReturnValueResult(valueToReturn);
51          } else if (valueIsArray && addCollectionOrMapWithElementsFromArray()) {
52              // Do nothing
53          } else if (hasReturnOfDifferentType()) {
54              addMultiValuedResult(valueIsArray);
55          } else {
56              invocationResults.addReturnValueResult(valueToReturn);
57          }
58      }
59  
60      private void addMultiValuedResult(boolean valueIsArray) {
61          if (valueIsArray) {
62              invocationResults.addResults(valueToReturn);
63          } else if (valueToReturn instanceof Iterable<?>) {
64              if (JAVA8 && valueToReturn instanceof Collection && returnType.isAssignableFrom(Stream.class)) {
65                  invocationResults.addReturnValueResult(((Collection<?>) valueToReturn).stream());
66              } else {
67                  invocationResults.addResults((Iterable<?>) valueToReturn);
68              }
69          } else {
70              invocationResults.addDeferredResults((Iterator<?>) valueToReturn);
71          }
72      }
73  
74      private boolean hasReturnOfDifferentType() {
75          return !returnType.isArray() && !Iterable.class.isAssignableFrom(returnType)
76                  && !Iterator.class.isAssignableFrom(returnType)
77                  && !returnType.isAssignableFrom(valueToReturn.getClass());
78      }
79  
80      private boolean addCollectionOrMapWithElementsFromArray() {
81          @NonNegative
82          int n = Array.getLength(valueToReturn);
83          Object values = null;
84  
85          if (returnType.isAssignableFrom(ListIterator.class)) {
86              List<Object> list = new ArrayList<>(n);
87              addArrayElements(list, n);
88              values = list.listIterator();
89          } else if (returnType.isAssignableFrom(List.class)) {
90              values = addArrayElements(new ArrayList<>(n), n);
91          } else if (returnType.isAssignableFrom(Set.class)) {
92              values = addArrayElements(new LinkedHashSet<>(n), n);
93          } else if (returnType.isAssignableFrom(SortedSet.class)) {
94              values = addArrayElements(new TreeSet<>(), n);
95          } else if (returnType.isAssignableFrom(Map.class)) {
96              values = addArrayElements(new LinkedHashMap<>(n), n);
97          } else if (returnType.isAssignableFrom(SortedMap.class)) {
98              values = addArrayElements(new TreeMap<>(), n);
99          } else if (JAVA8 && returnType.isAssignableFrom(Stream.class)) {
100             values = addArrayElements(new ArrayList<>(n), n).stream();
101         }
102 
103         if (values != null) {
104             invocationResults.addReturnValue(values);
105             return true;
106         }
107 
108         return false;
109     }
110 
111     @NonNull
112     private Collection<?> addArrayElements(@NonNull Collection<Object> values, @NonNegative int elementCount) {
113         for (int i = 0; i < elementCount; i++) {
114             Object element = Array.get(valueToReturn, i);
115             values.add(element);
116         }
117 
118         return values;
119     }
120 
121     @Nullable
122     private Object addArrayElements(@NonNull Map<Object, Object> values, @NonNegative int elementPairCount) {
123         for (int i = 0; i < elementPairCount; i++) {
124             Object keyAndValue = Array.get(valueToReturn, i);
125 
126             if (keyAndValue == null || !keyAndValue.getClass().isArray()) {
127                 return null;
128             }
129 
130             Object key = Array.get(keyAndValue, 0);
131             Object element = Array.getLength(keyAndValue) > 1 ? Array.get(keyAndValue, 1) : null;
132             values.put(key, element);
133         }
134 
135         return values;
136     }
137 }