77
88import static io .jooby .SneakyThrows .propagate ;
99
10- import java .lang .annotation .Annotation ;
11- import java .lang .reflect .AnnotatedElement ;
12- import java .lang .reflect .Constructor ;
13- import java .lang .reflect .Executable ;
14- import java .lang .reflect .InvocationTargetException ;
15- import java .lang .reflect .Method ;
16- import java .lang .reflect .Modifier ;
17- import java .lang .reflect .Parameter ;
10+ import java .lang .invoke .MethodHandles ;
11+ import java .lang .reflect .*;
1812import java .util .ArrayList ;
1913import java .util .Collection ;
2014import java .util .HashSet ;
3327import io .jooby .exception .BadRequestException ;
3428import io .jooby .exception .ProvisioningException ;
3529import io .jooby .internal .reflect .$Types ;
30+ import io .jooby .value .ConversionHint ;
31+ import io .jooby .value .Converter ;
3632import io .jooby .value .Value ;
3733import jakarta .inject .Inject ;
3834import jakarta .inject .Named ;
3935
40- public class ReflectiveBeanConverter {
36+ public class ReflectiveBeanConverter implements Converter {
4137
4238 private record Setter (Method method , Object arg ) {
43- public void invoke (Object instance ) throws InvocationTargetException , IllegalAccessException {
44- method .invoke (instance , arg );
39+ public void invoke (MethodHandles .Lookup lookup , Object instance ) throws Throwable {
40+ var handle = lookup .unreflect (method );
41+ handle .invoke (instance , arg );
4542 }
4643 }
4744
4845 private static final String AMBIGUOUS_CONSTRUCTOR =
4946 "Ambiguous constructor found. Expecting a single constructor or only one annotated with "
5047 + Inject .class .getName ();
5148
52- private static final Object [] NO_ARGS = new Object [ 0 ] ;
49+ private MethodHandles . Lookup lookup ;
5350
54- public Object convert (@ NonNull Value node , @ NonNull Class type , boolean allowEmptyBean ) {
51+ public ReflectiveBeanConverter (MethodHandles .Lookup lookup ) {
52+ this .lookup = lookup ;
53+ }
54+
55+ @ Override
56+ public Object convert (@ NonNull Type type , @ NonNull Value value , @ NonNull ConversionHint hint ) {
57+ return convert (value , $Types .parameterizedType0 (type ), hint == ConversionHint .Empty );
58+ }
59+
60+ private Object convert (@ NonNull Value node , @ NonNull Class type , boolean allowEmptyBean ) {
5561 try {
5662 return newInstance (type , node , allowEmptyBean );
57- } catch (InstantiationException | IllegalAccessException | NoSuchMethodException x ) {
58- throw propagate (x );
5963 } catch (InvocationTargetException x ) {
6064 throw propagate (x .getCause ());
65+ } catch (Throwable x ) {
66+ throw propagate (x );
6167 }
6268 }
6369
64- private static Object newInstance (Class type , Value node , boolean allowEmptyBean )
65- throws IllegalAccessException ,
66- InstantiationException ,
67- InvocationTargetException ,
68- NoSuchMethodException {
69- Constructor [] constructors = type .getConstructors ();
70+ private Object newInstance (Class type , Value node , boolean allowEmptyBean ) throws Throwable {
71+ var constructors = type .getConstructors ();
7072 Set <Value > state = new HashSet <>();
71- Constructor constructor ;
73+ Constructor <?> constructor ;
7274 if (constructors .length == 0 ) {
7375 constructor = type .getDeclaredConstructor ();
7476 } else {
7577 constructor = selectConstructor (constructors );
7678 }
77- Object [] args =
78- constructor .getParameterCount () == 0 ? NO_ARGS : inject (node , constructor , state ::add );
79- List <Setter > setters = setters (type , node , state );
79+ var args = inject (node , constructor , state ::add );
80+ var setters = setters (type , node , state );
8081 if (!allowEmptyBean (type , allowEmptyBean ) && state .stream ().allMatch (Value ::isMissing )) {
8182 return null ;
8283 }
83- var instance = constructor .newInstance (args );
84- for (Setter setter : setters ) {
85- setter .invoke (instance );
84+ var handle = lookup .unreflectConstructor (constructor );
85+ var instance = handle .invokeWithArguments (args );
86+ for (var setter : setters ) {
87+ setter .invoke (lookup , instance );
8688 }
8789 return instance ;
8890 }
@@ -97,9 +99,9 @@ private static Constructor selectConstructor(Constructor[] constructors) {
9799 } else {
98100 Constructor injectConstructor = null ;
99101 Constructor defaultConstructor = null ;
100- for (Constructor constructor : constructors ) {
102+ for (var constructor : constructors ) {
101103 if (Modifier .isPublic (constructor .getModifiers ())) {
102- Annotation inject = constructor .getAnnotation (Inject .class );
104+ var inject = constructor .getAnnotation (Inject .class );
103105 if (inject == null ) {
104106 if (constructor .getParameterCount () == 0 ) {
105107 defaultConstructor = constructor ;
@@ -113,31 +115,31 @@ private static Constructor selectConstructor(Constructor[] constructors) {
113115 }
114116 }
115117 }
116- Constructor result = injectConstructor == null ? defaultConstructor : injectConstructor ;
118+ var result = injectConstructor == null ? defaultConstructor : injectConstructor ;
117119 if (result == null ) {
118120 throw new IllegalStateException (AMBIGUOUS_CONSTRUCTOR );
119121 }
120122 return result ;
121123 }
122124 }
123125
124- public static Object [] inject (Value scope , Executable method , Consumer <Value > state ) {
125- Parameter [] parameters = method .getParameters ();
126+ public static List < Object > inject (Value scope , Executable method , Consumer <Value > state ) {
127+ var parameters = method .getParameters ();
126128 if (parameters .length == 0 ) {
127- return NO_ARGS ;
129+ return List . of () ;
128130 }
129- Object [] args = new Object [ parameters .length ] ;
131+ var args = new ArrayList <>( parameters .length ) ;
130132 for (int i = 0 ; i < parameters .length ; i ++) {
131- Parameter parameter = parameters [i ];
132- String name = paramName (parameter );
133- Value param = scope .get (name );
133+ var parameter = parameters [i ];
134+ var name = paramName (parameter );
135+ var param = scope .get (name );
134136 var arg = value (parameter , scope , param );
135137 if (arg == null ) {
136138 state .accept (Value .missing (name ));
137139 } else {
138140 state .accept (param );
139141 }
140- args [ i ] = arg ;
142+ args . add ( arg ) ;
141143 }
142144 return args ;
143145 }
@@ -176,13 +178,13 @@ private static List<Setter> setters(Class type, Value node, Set<Value> nodes) {
176178 var methods = type .getMethods ();
177179 var result = new ArrayList <Setter >();
178180 for (String name : names (node )) {
179- Value value = node .get (name );
181+ var value = node .get (name );
180182 if (nodes .add (value )) {
181- Method method = findSetter (methods , name );
183+ var method = findSetter (methods , name );
182184 if (method != null ) {
183- Parameter parameter = method .getParameters ()[0 ];
185+ var parameter = method .getParameters ()[0 ];
184186 try {
185- Object arg = value (parameter , node , value );
187+ var arg = value (parameter , node , value );
186188 result .add (new Setter (method , arg ));
187189 } catch (ProvisioningException x ) {
188190 throw x ;
@@ -200,9 +202,9 @@ private static List<Setter> setters(Class type, Value node, Set<Value> nodes) {
200202 private static Object value (Parameter parameter , Value node , Value value ) {
201203 try {
202204 if (isFileUpload (node , parameter )) {
203- Formdata formdata = (Formdata ) node ;
205+ var formdata = (Formdata ) node ;
204206 if (Set .class .isAssignableFrom (parameter .getType ())) {
205- return new HashSet <>(formdata .files (value .name ()));
207+ return new LinkedHashSet <>(formdata .files (value .name ()));
206208 } else if (Collection .class .isAssignableFrom (parameter .getType ())) {
207209 return formdata .files (value .name ());
208210 } else if (Optional .class .isAssignableFrom (parameter .getType ())) {
@@ -222,7 +224,7 @@ private static Object value(Parameter parameter, Value node, Value value) {
222224 if (isNullable (parameter )) {
223225 if (value .isSingle ()) {
224226 var str = value .valueOrNull ();
225- if (str == null || str .length () == 0 ) {
227+ if (str == null || str .isEmpty () ) {
226228 // treat empty values as null
227229 return null ;
228230 }
@@ -252,7 +254,7 @@ private static boolean isNullable(Parameter parameter) {
252254
253255 private static boolean hasAnnotation (AnnotatedElement element , String ... names ) {
254256 var nameList = List .of (names );
255- for (Annotation annotation : element .getAnnotations ()) {
257+ for (var annotation : element .getAnnotations ()) {
256258 if (nameList .stream ()
257259 .anyMatch (name -> annotation .annotationType ().getSimpleName ().endsWith (name ))) {
258260 return true ;
@@ -273,7 +275,7 @@ private static boolean isFileUpload(Class type) {
273275 private static Method findSetter (Method [] methods , String name ) {
274276 var setter = "set" + Character .toUpperCase (name .charAt (0 )) + name .substring (1 );
275277 var candidates = new LinkedList <Method >();
276- for (Method method : methods ) {
278+ for (var method : methods ) {
277279 if ((method .getName ().equals (name ) || method .getName ().equals (setter ))
278280 && method .getParameterCount () == 1 ) {
279281 if (method .getName ().startsWith ("set" )) {
0 commit comments