1414import edu .umd .cs .findbugs .annotations .NonNull ;
1515import edu .umd .cs .findbugs .annotations .Nullable ;
1616import io .jooby .SneakyThrows ;
17+ import io .jooby .exception .ProvisioningException ;
1718import io .jooby .exception .TypeMismatchException ;
1819import io .jooby .internal .converter .ReflectiveBeanConverter ;
1920import io .jooby .internal .converter .StandardConverter ;
2021import io .jooby .internal .reflect .$Types ;
2122
23+ /**
24+ * Keep track of existing {@link Converter} and convert values to a more concrete type. This class
25+ * resolve all the <code>toXXX</code> calls from {@link Value}.
26+ *
27+ * <ul>
28+ * <li>{@link Value#to(Class)}: convert to request type using {@link ConversionHint#Strict}
29+ * <li>{@link Value#toNullable(Class)}: convert to request type using {@link
30+ * ConversionHint#Nullable}
31+ * <li>{@link io.jooby.QueryString#toEmpty(Class)}: convert to request type using {@link
32+ * ConversionHint#Empty}
33+ * </ul>
34+ *
35+ * @author edgar
36+ * @since 4.0.0
37+ */
2238public class ValueFactory {
2339
2440 private final Map <Type , Converter > converterMap = new HashMap <>();
@@ -29,42 +45,134 @@ public class ValueFactory {
2945
3046 private Converter fallback ;
3147
48+ /**
49+ * Creates a new instance.
50+ *
51+ * @param lookup Lookup to use.
52+ */
3253 public ValueFactory (@ NonNull MethodHandles .Lookup lookup ) {
3354 this .lookup = lookup ;
3455 this .fallback = new ReflectiveBeanConverter (lookup );
3556 StandardConverter .register (this );
3657 }
3758
59+ /** Creates a new instance with public lookup. */
3860 public ValueFactory () {
3961 this (MethodHandles .publicLookup ());
4062 }
4163
64+ /**
65+ * Set lookup to use. Required by:
66+ *
67+ * <ul>
68+ * <li>valueOf(String) converter
69+ * <li>constructor(String) converter
70+ * <li>fallback/reflective bean converter
71+ * </ul>
72+ *
73+ * @param lookup Look up to use.
74+ * @return This instance.
75+ */
4276 public @ NonNull ValueFactory lookup (@ NonNull MethodHandles .Lookup lookup ) {
4377 this .lookup = lookup ;
4478 this .fallback = new ReflectiveBeanConverter (lookup );
4579 return this ;
4680 }
4781
82+ /**
83+ * Set default conversion hint to use. Defaults is {@link ConversionHint#Strict}.
84+ *
85+ * @param defaultHint Default conversion hint.
86+ * @return This instance.
87+ */
4888 public @ NonNull ValueFactory hint (@ NonNull ConversionHint defaultHint ) {
4989 this .defaultHint = defaultHint ;
5090 return this ;
5191 }
5292
93+ /**
94+ * Get a converter for the given type. This is an exact lookup, no inheritance rule applies here.
95+ *
96+ * @param type The requested type.
97+ * @return A converter or <code>null</code>.
98+ */
5399 public @ Nullable Converter get (Type type ) {
54100 return converterMap .get (type );
55101 }
56102
103+ /**
104+ * Set a custom converter for type.
105+ *
106+ * @param type Target type.
107+ * @param converter Converter.
108+ * @return This instance.
109+ */
57110 public @ NonNull ValueFactory put (@ NonNull Type type , @ NonNull Converter converter ) {
58111 converterMap .put (type , converter );
59112 return this ;
60113 }
61114
62- public <T > T convert (@ NonNull Type type , @ NonNull Value value ) {
115+ /**
116+ * Convert a value to target type using the default {@link #hint(ConversionHint)}. Conversion
117+ * steps:
118+ *
119+ * <ul>
120+ * <li>Find a converter by type and use it. If no converter is found:
121+ * <li>Find a factory method <code>valueOf(String)</code> for {@link Value#isSingle()} values
122+ * and use it. If no converter is found:
123+ * <li>Find a <code>constructor(String)</code> for {@link Value#isSingle()} values. If no
124+ * converter is found:
125+ * <li>Fallback to reflective converter.
126+ * </ul>
127+ *
128+ * @param type Target type.
129+ * @param value Value.
130+ * @param <T> Target type.
131+ * @return New instance.
132+ * @throws TypeMismatchException when convert returns <code>null</code> and hint is set to {@link
133+ * ConversionHint#Strict}.
134+ * @throws ProvisioningException when convert target type constructor requires a non-null value
135+ * and value is missing or null.
136+ */
137+ public <T > T convert (@ NonNull Type type , @ NonNull Value value )
138+ throws TypeMismatchException , ProvisioningException {
63139 return convert (type , value , defaultHint );
64140 }
65141
142+ /**
143+ * Convert a value to target type using a hint. Conversion steps:
144+ *
145+ * <ul>
146+ * <li>Find a converter by type and use it. If no converter is found:
147+ * <li>Find a factory method <code>valueOf(String)</code> for {@link Value#isSingle()} values
148+ * and use it. If no converter is found:
149+ * <li>Find a <code>constructor(String)</code> for {@link Value#isSingle()} values. If no
150+ * converter is found:
151+ * <li>Fallback to reflective converter.
152+ * </ul>
153+ *
154+ * @param type Target type.
155+ * @param value Value.
156+ * @param hint Conversion hint.
157+ * @param <T> Target type.
158+ * @return New instance.
159+ * @throws TypeMismatchException when convert returns <code>null</code> and hint is set to {@link
160+ * ConversionHint#Strict}.
161+ * @throws ProvisioningException when convert target type constructor requires a non-null value
162+ * and value is missing or null.
163+ */
164+ public <T > T convert (@ NonNull Type type , @ NonNull Value value , @ NonNull ConversionHint hint )
165+ throws TypeMismatchException , ProvisioningException {
166+ T result = convertInternal (type , value , hint );
167+ if (result == null && hint == ConversionHint .Strict ) {
168+ throw new TypeMismatchException (value .name (), type );
169+ }
170+ return result ;
171+ }
172+
66173 @ SuppressWarnings ("unchecked" )
67- public <T > T convert (@ NonNull Type type , @ NonNull Value value , @ NonNull ConversionHint hint ) {
174+ private <T > T convertInternal (
175+ @ NonNull Type type , @ NonNull Value value , @ NonNull ConversionHint hint ) {
68176 var converter = converterMap .get (type );
69177 if (converter != null ) {
70178 // Specific converter at type level.
@@ -95,11 +203,7 @@ public <T> T convert(@NonNull Type type, @NonNull Value value, @NonNull Conversi
95203 }
96204 }
97205 // anything else fallback to reflective
98- var result = (T ) fallback .convert (type , value , hint );
99- if (result == null && hint == ConversionHint .Strict ) {
100- throw new TypeMismatchException (value .name (), type );
101- }
102- return result ;
206+ return (T ) fallback .convert (type , value , hint );
103207 }
104208 }
105209
0 commit comments