1
16 package org.modelmapper.internal;
17
18 import java.io.PrintWriter;
19 import java.io.StringWriter;
20 import java.lang.reflect.Field;
21 import java.lang.reflect.Member;
22 import java.lang.reflect.Method;
23 import java.lang.reflect.Type;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.Formatter;
28 import java.util.List;
29 import org.modelmapper.ConfigurationException;
30 import org.modelmapper.MappingException;
31 import org.modelmapper.TypeMap;
32 import org.modelmapper.ValidationException;
33 import org.modelmapper.internal.util.Strings;
34 import org.modelmapper.internal.util.Types;
35 import org.modelmapper.spi.ErrorMessage;
36 import org.modelmapper.spi.PropertyInfo;
37 import org.modelmapper.spi.PropertyMapping;
38
39 public final class Errors {
40 private List<ErrorMessage> errors;
41
42 @SuppressWarnings("rawtypes") private static final Converter<?>[] converters = new Converter[] {
43 new Converter<Class>(Class.class) {
44 public String toString(Class type) {
45 return type.getName();
46 }
47 }, new Converter<Member>(Member.class) {
48 public String toString(Member member) {
49 return Types.toString(member);
50 }
51 }, new Converter<PropertyInfo>(PropertyInfo.class) {
52 public String toString(PropertyInfo propertyInfo) {
53 return Types.toString(propertyInfo.getMember());
54 }
55 }, new Converter<Collection>(Collection.class) {
56 public String toString(Collection collection) {
57 StringBuilder builder = new StringBuilder();
58 boolean first = true;
59 for (Object o : collection) {
60 if (first)
61 first = false;
62 else
63 builder.append("\n");
64 builder.append("\t").append(Errors.convert(o));
65 }
66 return builder.toString();
67 }
68 } };
69
70 private static abstract class Converter<T> {
71 final Class<T> type;
72
73 Converter(Class<T> type) {
74 this.type = type;
75 }
76
77 boolean appliesTo(Object subject) {
78 return subject != null && type.isAssignableFrom(subject.getClass());
79 }
80
81 String convert(Object subject) {
82 return toString(type.cast(subject));
83 }
84
85 abstract String toString(T subject);
86 }
87
88 public Errors() {
89 }
90
91
92 public static String format(String heading, Collection<ErrorMessage> errorMessages) {
93 @SuppressWarnings("resource")
94 Formatter fmt = new Formatter().format(heading).format(":%n%n");
95 int index = 1;
96 boolean displayCauses = getOnlyCause(errorMessages) == null;
97
98 for (ErrorMessage errorMessage : errorMessages) {
99 fmt.format("%s) %s%n", index++, errorMessage.getMessage());
100
101 Throwable cause = errorMessage.getCause();
102 if (displayCauses && cause != null) {
103 StringWriter writer = new StringWriter();
104 cause.printStackTrace(new PrintWriter(writer));
105 fmt.format("Caused by: %s", writer.getBuffer());
106 }
107
108 fmt.format("%n");
109 }
110
111 if (errorMessages.size() == 1)
112 fmt.format("1 error");
113 else
114 fmt.format("%s errors", errorMessages.size());
115
116 return fmt.toString();
117 }
118
119 public static String format(String messageFormat, Object... arguments) {
120 for (int i = 0; i < arguments.length; i++)
121 arguments[i] = Errors.convert(arguments[i]);
122 return String.format(messageFormat, arguments);
123 }
124
125
129 public static Throwable getOnlyCause(Collection<ErrorMessage> messages) {
130 Throwable onlyCause = null;
131 for (ErrorMessage message : messages) {
132 Throwable messageCause = message.getCause();
133 if (messageCause == null) {
134 continue;
135 }
136
137 if (onlyCause != null) {
138 return null;
139 }
140
141 onlyCause = messageCause;
142 }
143
144 return onlyCause;
145 }
146
147 private static Object convert(Object source) {
148 for (Converter<?> converter : converters)
149 if (converter.appliesTo(source))
150 return converter.convert(source);
151 return source;
152 }
153
154 public Errors addMessage(Throwable cause, String message, Object... arguments) {
155 addMessage(new ErrorMessage(format(message, arguments), cause));
156 return this;
157 }
158
159 public Errors addMessage(ErrorMessage message) {
160 if (errors == null)
161 errors = new ArrayList<ErrorMessage>();
162 errors.add(message);
163 return this;
164 }
165
166 public Errors addMessage(String message, Object... arguments) {
167 return addMessage(null, message, arguments);
168 }
169
170 public Errors errorGettingValue(Member member, Throwable t) {
171 return addMessage(t, "Failed to get value from %s", member);
172 }
173
174 public Errors errorInstantiatingDestination(Class<?> type, Throwable t) {
175 return addMessage(
176 t,
177 "Failed to instantiate instance of destination %s. Ensure that %s has a non-private no-argument constructor.",
178 type, type);
179 }
180
181 public Errors errorMapping(Object source, Class<?> destinationType) {
182 return addMessage("Error mapping %s to %s", source, Types.toString(destinationType));
183 }
184
185 public Errors errorMapping(Object source, Type destinationType, Throwable t) {
186 return addMessage(t, "Error mapping %s to %s", source, Types.toString(destinationType));
187 }
188
189 public Errors errorSettingValue(Member member, Object value, Throwable t) {
190 return addMessage(t, "Failed to set value '%s' on %s", value, member);
191 }
192
193 public Errors errorTooLarge(Object source, Class<?> destinationType) {
194 return addMessage("Value '%s' is too large for %s", source, Types.toString(destinationType));
195 }
196
197 public Errors errorTooSmall(Object source, Class<?> destinationType) {
198 return addMessage("Value '%s' is too small for %s", source, Types.toString(destinationType));
199 }
200
201 public Errors errorUnmappedProperties(TypeMap<?, ?> typeMap, List<PropertyInfo> unmappedProperties) {
202 return addMessage("Unmapped destination properties found in %s:\n\n%s", typeMap,
203 unmappedProperties);
204 }
205
206 public Errors errorUnsupportedMapping(Class<?> sourceType, Class<?> destinationType) {
207 return addMessage("Missing type map configuration or unsupported mapping for %s to %s.",
208 sourceType, destinationType);
209 }
210
211 public List<ErrorMessage> getMessages() {
212 if (errors == null)
213 return Collections.emptyList();
214 return errors;
215 }
216
217 public boolean hasErrors() {
218 return errors != null;
219 }
220
221 public Errors invalidProvidedDestinationInstance(Object destination, Class<?> requiredType) {
222 return addMessage("The provided destination instance %s is not of the required type %s.",
223 destination, requiredType);
224 }
225
226 public Errors merge(Collection<ErrorMessage> errorMessages) {
227 for (ErrorMessage message : errorMessages)
228 addMessage(message);
229 return this;
230 }
231
232 public Errors merge(Errors errors) {
233 for (ErrorMessage message : errors.getMessages())
234 addMessage(message);
235 return this;
236 }
237
238 public void throwConfigurationExceptionIfErrorsExist() {
239 if (hasErrors())
240 throw new ConfigurationException(getMessages());
241 }
242
243 public void throwValidationExceptionIfErrorsExist() {
244 if (hasErrors())
245 throw new ValidationException(getMessages());
246 }
247
248 public ConfigurationException toConfigurationException() {
249 return new ConfigurationException(getMessages());
250 }
251
252 public ErrorsException toException() {
253 return new ErrorsException(this);
254 }
255
256 public MappingException toMappingException() {
257 return new MappingException(getMessages());
258 }
259
260 Errors ambiguousDestination(List<? extends PropertyMapping> mappings) {
261 List<String> sourcePropertyInfo = new ArrayList<String>();
262 for (PropertyMapping mapping : mappings)
263 sourcePropertyInfo.add(Strings.joinMembers(mapping.getSourceProperties()));
264
265 return addMessage(
266 "The destination property %s matches multiple source property hierarchies:\n\n%s",
267 Strings.joinMembers(mappings.get(0).getDestinationProperties()), sourcePropertyInfo);
268 }
269
270 Errors conditionalSkipWithoutSource() {
271 return addMessage("A conditional skip can only be used with skip(Object, Object).");
272 }
273
274 Errors duplicateMapping(PropertyInfo destinationProperty) {
275 return addMessage("A mapping already exists for %s.", destinationProperty);
276 }
277
278 Errors errorAccessingConfigure(Throwable t) {
279 return addMessage(t, "Failed to access PropertyMap.configure().");
280 }
281
282 Errors errorAccessingProperty(PropertyInfo propertyInfo) {
283 return addMessage("Failed to access %s.", propertyInfo);
284 }
285
286 Errors errorConverting(org.modelmapper.Converter<?, ?> converter, Class<?> sourceType,
287 Class<?> destinationType, Throwable throwable) {
288 return addMessage(throwable, "Converter %s failed to convert %s to %s.", converter, sourceType,
289 destinationType);
290 }
291
292 Errors errorEnhancingClass(Class<?> type, Throwable t) {
293 return addMessage(t,
294 "Failed to generate proxy class for %s. Ensure that %s has a non-private constructor.",
295 type, type);
296 }
297
298 Errors errorInstantiatingProxy(Class<?> type, Throwable t) {
299 return addMessage(
300 t,
301 "Failed to instantiate proxied instance of %s. Ensure that %s has a non-private constructor.",
302 type, type);
303 }
304
305 Errors errorResolvingClass(Throwable t, String className) {
306 return addMessage("Error resolving class %s", className);
307 }
308
309 Errors errorReadingClass(Throwable t, String className) {
310 return addMessage("Error reading class %s", className);
311 }
312
313 Errors errorInvalidSourcePath(String sourcePath, Class<?> unresolveableType,
314 String unresolveableProperty) {
315 return addMessage("The source path %s is invalid: %s.%s cannot be resolved.", sourcePath,
316 unresolveableType, unresolveableProperty);
317 }
318
319 Errors errorInvalidDestinationPath(String destinationPath, Class<?> unresolveableType,
320 String unresolveableProperty) {
321 return addMessage("The destination path %s is invalid: %s.%s cannot be resolved.", destinationPath,
322 unresolveableType, unresolveableProperty);
323 }
324
325 Errors errorNullArgument(String parameter) {
326 return addMessage("The %s cannot be null", parameter);
327 }
328
329 Errors invalidDestinationMethod(Method method) {
330 return addMessage(
331 "Invalid destination method %s. Ensure that method has one parameter and returns void.",
332 method);
333 }
334
335 Errors invalidDestinationField(Field field) {
336 return addMessage("Invalid destination field %s. Ensure that field is not static.", field);
337 }
338
339 Errors invalidSourceMethod(Method method) {
340 return addMessage(
341 "Invalid source method %s. Ensure that method has zero parameters and does not return void.",
342 method);
343 }
344
345 Errors invalidSourceField(Field field) {
346 return addMessage("Invalid source field %s. Ensure that field is not static.", field);
347 }
348
349 Errors invocationAgainstFinalClass(Class<?> type) {
350 return addMessage("Cannot map final type %s.", type);
351 }
352
353 Errors invocationAgainstFinalMethod(Member member) {
354 return addMessage("Cannot map final method %s.", member);
355 }
356
357 Errors mappingForEnum() {
358 return addMessage("Cannot create mapping for enum.");
359 }
360
361 Errors missingDestination() {
362 return addMessage("A mapping is missing a required destination member.");
363 }
364
365 Errors missingMutatorForAccessor(Method method) {
366 return addMessage("No corresponding mutator was found for %s.", method);
367 }
368
369 Errors missingSource() {
370 return addMessage("A mapping is missing a required source member.");
371 }
372
373 Errors sourceOutsideOfMap() {
374 return addMessage("'source' cannot be used outside of a map statement.");
375 }
376
377 Errors skipConflict(String skip, List<String> paths) {
378 return addMessage("Not able to skip %s, because there are already nested properties are mapped: [%s]. "
379 + "Do you skip the property after the implicit mappings mapped? "
380 + "We recommended you to create an empty type map, and followed by addMappings and implicitMappings calls",
381 skip, String.join(" ", paths));
382 }
383
384 void throwMappingExceptionIfErrorsExist() {
385 if (hasErrors())
386 throw new MappingException(getMessages());
387 }
388 }
389