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.argumentMatching;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import java.lang.reflect.Array;
12  import java.lang.reflect.Method;
13  
14  import mockit.internal.util.ObjectMethods;
15  
16  public final class ArgumentMismatch {
17      @NonNull
18      private final StringBuilder out = new StringBuilder(50);
19      @Nullable
20      private String parameterType;
21  
22      @Nullable
23      public String getParameterType() {
24          return parameterType;
25      }
26  
27      @Override
28      public String toString() {
29          return out.toString();
30      }
31  
32      @NonNull
33      public ArgumentMismatch append(char c) {
34          out.append(c);
35          return this;
36      }
37  
38      @NonNull
39      public ArgumentMismatch append(int i) {
40          out.append(i);
41          return this;
42      }
43  
44      @NonNull
45      public ArgumentMismatch append(double d) {
46          out.append(d);
47          return this;
48      }
49  
50      @NonNull
51      public ArgumentMismatch append(@Nullable CharSequence str) {
52          out.append(str);
53          return this;
54      }
55  
56      public void appendFormatted(@Nullable String parameterTypeName, @Nullable Object argumentValue,
57              @Nullable ArgumentMatcher<?> matcher) {
58          if (matcher == null) {
59              appendFormatted(argumentValue);
60          } else {
61              parameterType = parameterTypeName;
62              matcher.writeMismatchPhrase(this);
63          }
64      }
65  
66      @SuppressWarnings("OverlyComplexMethod")
67      void appendFormatted(@Nullable Object value) {
68          if (value == null) {
69              out.append("null");
70          } else if (value instanceof CharSequence) {
71              appendCharacters((CharSequence) value);
72          } else if (value instanceof Character) {
73              out.append('\'');
74              appendEscapedOrPlainCharacter('\'', (Character) value);
75              out.append('\'');
76          } else if (value instanceof Byte) {
77              out.append(value).append('b');
78          } else if (value instanceof Short) {
79              out.append(value).append('s');
80          } else if (value instanceof Long) {
81              out.append(value).append('L');
82          } else if (value instanceof Float) {
83              out.append(value).append('F');
84          } else if (value instanceof Number || value instanceof Boolean) {
85              out.append(value);
86          } else if (value.getClass().isArray()) {
87              appendArray(value);
88          } else if (value instanceof ArgumentMatcher) {
89              ((ArgumentMatcher<?>) value).writeMismatchPhrase(this);
90          } else {
91              appendArbitraryArgument(value);
92          }
93      }
94  
95      private void appendArray(@NonNull Object array) {
96          out.append('[');
97          String separator = "";
98  
99          for (int i = 0, n = Array.getLength(array); i < n; i++) {
100             Object nextValue = Array.get(array, i);
101             out.append(separator);
102             appendFormatted(nextValue);
103             separator = ", ";
104         }
105 
106         out.append(']');
107     }
108 
109     private void appendCharacters(@NonNull CharSequence characters) {
110         out.append('"');
111 
112         for (int i = 0, n = characters.length(); i < n; i++) {
113             char c = characters.charAt(i);
114             appendEscapedOrPlainCharacter('"', c);
115         }
116 
117         out.append('"');
118     }
119 
120     private void appendEscapedOrPlainCharacter(char quoteCharacter, char c) {
121         if (c == quoteCharacter) {
122             out.append('\\').append(c);
123         } else {
124             switch (c) {
125                 case '\t':
126                     out.append("\\t");
127                     break;
128                 case '\n':
129                     out.append("\\n");
130                     break;
131                 case '\r':
132                     out.append("\\r");
133                     break;
134                 default:
135                     out.append(c);
136             }
137         }
138     }
139 
140     private void appendArbitraryArgument(@NonNull Object value) {
141         Class<?> valueClass = value.getClass();
142 
143         Method toStringMethod;
144         try {
145             toStringMethod = valueClass.getMethod("toString");
146         } catch (NoSuchMethodException ignored) {
147             return;
148         }
149 
150         if (toStringMethod.getDeclaringClass() == Object.class) {
151             out.append(value);
152         } else {
153             String valueAsString = value.toString();
154 
155             if (valueAsString != null && !valueAsString.isEmpty()) {
156                 appendCharacters(valueAsString);
157             } else {
158                 out.append(ObjectMethods.objectIdentity(value));
159             }
160         }
161     }
162 
163     public void appendFormatted(@NonNull Object[] values) {
164         String separator = "";
165 
166         for (Object value : values) {
167             append(separator).appendFormatted(value);
168             separator = ", ";
169         }
170     }
171 }