1 /*
2  * Copyright 2011 the original author or authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

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   /** Returns the formatted message for an exception with the specified messages. */
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   /**
126    * Returns the cause throwable if there is exactly one cause in {@code messages}. If there are
127    * zero or multiple messages with causes, null is returned.
128    */

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