-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Formatters.java
317 lines (272 loc) · 11.1 KB
/
Formatters.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
/*
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
*/
package play.data.format;
import org.springframework.core.*;
import org.springframework.core.convert.*;
import org.springframework.context.i18n.*;
import org.springframework.format.support.*;
import org.springframework.core.convert.converter.*;
import java.util.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import javax.inject.Inject;
import javax.inject.Singleton;
import play.i18n.MessagesApi;
/**
* Formatters helper.
*/
@Singleton
public class Formatters {
@Inject
public Formatters(MessagesApi messagesApi) {
// By default, we always register some common and useful Formatters
register(Date.class, new Formats.DateFormatter(messagesApi));
register(Date.class, new Formats.AnnotationDateFormatter(messagesApi));
register(String.class, new Formats.AnnotationNonEmptyFormatter());
registerOptional();
}
/**
* Parses this string as instance of the given class.
*
* @param text the text to parse
* @param clazz class representing the required type
* @param <T> the type to parse out of the text
* @return the parsed value
*/
public <T> T parse(String text, Class<T> clazz) {
return conversion.convert(text, clazz);
}
/**
* Parses this string as instance of a specific field
*
* @param field the related field (custom formatters are extracted from this field annotation)
* @param text the text to parse
* @param <T> the type to parse out of the text
* @return the parsed value
*/
@SuppressWarnings("unchecked")
public <T> T parse(Field field, String text) {
return (T)conversion.convert(text, new TypeDescriptor(field));
}
/**
* Computes the display string for any value.
*
* @param t the value to print
* @param <T> the type to print
* @return the formatted string
*/
public <T> String print(T t) {
if(t == null) {
return "";
}
if(conversion.canConvert(t.getClass(), String.class)) {
return conversion.convert(t, String.class);
} else {
return t.toString();
}
}
/**
* Computes the display string for any value, for a specific field.
*
* @param field the related field - custom formatters are extracted from this field annotation
* @param t the value to print
* @param <T> the type to print
* @return the formatted string
*/
public <T> String print(Field field, T t) {
return print(new TypeDescriptor(field), t);
}
/**
* Computes the display string for any value, for a specific type.
*
* @param desc the field descriptor - custom formatters are extracted from this descriptor.
* @param t the value to print
* @param <T> the type to print
* @return the formatted string
*/
public <T> String print(TypeDescriptor desc, T t) {
if(t == null) {
return "";
}
if(desc != null && conversion.canConvert(desc, TypeDescriptor.valueOf(String.class))) {
return (String)conversion.convert(t, desc, TypeDescriptor.valueOf(String.class));
} else if(conversion.canConvert(t.getClass(), String.class)) {
return conversion.convert(t, String.class);
} else {
return t.toString();
}
}
// --
/**
* The underlying conversion service.
*/
public final FormattingConversionService conversion = new FormattingConversionService();
/**
* Super-type for custom simple formatters.
*
* @param <T> the type that this formatter will parse and print
*/
public static abstract class SimpleFormatter<T> {
/**
* Binds the field - constructs a concrete value from submitted data.
*
* @param text the field text
* @param locale the current Locale
* @throws java.text.ParseException if the text could not be parsed into T
* @return a new value
*/
public abstract T parse(String text, Locale locale) throws java.text.ParseException;
/**
* Unbinds this field - transforms a concrete value to plain string.
*
* @param t the value to unbind
* @param locale the current <code>Locale</code>
* @return printable version of the value
*/
public abstract String print(T t, Locale locale);
}
/**
* Super-type for annotation-based formatters.
*
* @param <A> the type of the annotation
* @param <T> the type that this formatter will parse and print
*/
public static abstract class AnnotationFormatter<A extends Annotation,T> {
/**
* Binds the field - constructs a concrete value from submitted data.
*
* @param annotation the annotation that triggered this formatter
* @param text the field text
* @param locale the current <code>Locale</code>
* @throws java.text.ParseException when the text could not be parsed
* @return a new value
*/
public abstract T parse(A annotation, String text, Locale locale) throws java.text.ParseException;
/**
* Unbind this field (ie. transform a concrete value to plain string)
*
* @param annotation the annotation that triggered this formatter.
* @param value the value to unbind
* @param locale the current <code>Locale</code>
* @return printable version of the value
*/
public abstract String print(A annotation, T value, Locale locale);
}
/**
* Converter for String -> Optional and Optional -> String
*/
private Formatters registerOptional() {
conversion.addConverter(new GenericConverter() {
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
if (sourceType.getObjectType().equals(String.class)) {
// From String to Optional
Object element = conversion.convert(source, sourceType, targetType.elementTypeDescriptor(source));
return Optional.ofNullable(element);
} else if (targetType.getObjectType().equals(String.class)) {
// From Optional to String
if (source == null) return "";
Optional<?> opt = (Optional) source;
return opt.map(o -> conversion.convert(source, sourceType.getElementTypeDescriptor(), targetType))
.orElse("");
}
return null;
}
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
Set<ConvertiblePair> result = new HashSet<>();
result.add(new ConvertiblePair(Optional.class, String.class));
result.add(new ConvertiblePair(String.class, Optional.class));
return result;
}
});
return this;
}
/**
* Registers a simple formatter.
*
* @param clazz class handled by this formatter
* @param <T> the type that this formatter will parse and print
* @param formatter the formatter to register
* @return the modified Formatters object.
*/
public <T> Formatters register(final Class<T> clazz, final SimpleFormatter<T> formatter) {
conversion.addFormatterForFieldType(clazz, new org.springframework.format.Formatter<T>() {
public T parse(String text, Locale locale) throws java.text.ParseException {
return formatter.parse(text, locale);
}
public String print(T t, Locale locale) {
return formatter.print(t, locale);
}
public String toString() {
return formatter.toString();
}
});
return this;
}
/**
* Registers an annotation-based formatter.
*
* @param clazz class handled by this formatter
* @param formatter the formatter to register
* @param <A> the annotation type
* @param <T> the type that will be parsed or printed
* @return the modified Formatters object.
*/
@SuppressWarnings("unchecked")
public <A extends Annotation,T> Formatters register(final Class<T> clazz, final AnnotationFormatter<A,T> formatter) {
final Class<? extends Annotation> annotationType = (Class<? extends Annotation>)GenericTypeResolver.resolveTypeArguments(
formatter.getClass(), AnnotationFormatter.class
)[0];
conversion.addConverter(new ConditionalGenericConverter() {
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
Set<GenericConverter.ConvertiblePair> types = new HashSet<>();
types.add(new GenericConverter.ConvertiblePair(clazz, String.class));
return types;
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (sourceType.getAnnotation(annotationType) != null);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final A a = (A)sourceType.getAnnotation(annotationType);
Locale locale = LocaleContextHolder.getLocale();
try {
return formatter.print(a, (T)source, locale);
} catch (Exception ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
}
public String toString() {
return "@" + annotationType.getName() + " "
+ clazz.getName() + " -> "
+ String.class.getName() + ": "
+ formatter;
}
});
conversion.addConverter(new ConditionalGenericConverter() {
public Set<GenericConverter.ConvertiblePair> getConvertibleTypes() {
Set<GenericConverter.ConvertiblePair> types = new HashSet<>();
types.add(new GenericConverter.ConvertiblePair(String.class, clazz));
return types;
}
public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
return (targetType.getAnnotation(annotationType) != null);
}
public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
final A a = (A)targetType.getAnnotation(annotationType);
Locale locale = LocaleContextHolder.getLocale();
try {
return formatter.parse(a, (String)source, locale);
} catch (Exception ex) {
throw new ConversionFailedException(sourceType, targetType, source, ex);
}
}
public String toString() {
return String.class.getName() + " -> @"
+ annotationType.getName() + " "
+ clazz.getName() + ": "
+ formatter;
}
});
return this;
}
}