1818 */
1919package org .jooby .internal .parser ;
2020
21- import java .lang .reflect .Constructor ;
22- import java .lang .reflect .Field ;
23- import java .lang .reflect .Modifier ;
24- import java .util .Arrays ;
25- import java .util .List ;
2621import java .util .Map ;
27- import java .util .Map . Entry ;
22+ import java .util .concurrent . ConcurrentHashMap ;
2823import java .util .function .Function ;
29- import java .util .stream .Collectors ;
30-
31- import javax .inject .Inject ;
3224
3325import org .jooby .Err ;
3426import org .jooby .Mutant ;
3729import org .jooby .Response ;
3830import org .jooby .internal .ParameterNameProvider ;
3931import org .jooby .internal .mvc .RequestParam ;
40- import org .jooby .internal .mvc .RequestParamNameProviderImpl ;
41- import org .jooby .internal .mvc .RequestParamProvider ;
42- import org .jooby .internal .mvc .RequestParamProviderImpl ;
43- import org .slf4j .LoggerFactory ;
4432
45- import com .google .common .base .CharMatcher ;
46- import com .google .common .base .Splitter ;
4733import com .google .common .primitives .Primitives ;
4834import com .google .common .reflect .Reflection ;
4935import com .google .inject .TypeLiteral ;
@@ -60,8 +46,12 @@ public class BeanParser implements Parser {
6046
6147 private Function <? super Throwable , Try <? extends Object >> recoverMissing ;
6248
49+ @ SuppressWarnings ("rawtypes" )
50+ private final Map <Class , BeanPlan > beans ;
51+
6352 public BeanParser (final boolean allowNulls ) {
6453 this .recoverMissing = allowNulls ? MISSING : RETHROW ;
54+ this .beans = new ConcurrentHashMap <>();
6555 }
6656
6757 @ Override
@@ -79,7 +69,7 @@ public Object parse(final TypeLiteral<?> type, final Context ctx) throws Throwab
7969 bean = newBean (ctx .require (Request .class ), ctx .require (Response .class ), map , beanType );
8070 }
8171
82- return bean == null ? ctx . next () : bean ;
72+ return bean ;
8373 });
8474 }
8575
@@ -90,75 +80,13 @@ public String toString() {
9080
9181 private Object newBean (final Request req , final Response rsp ,
9282 final Map <String , Mutant > params , final Class <?> beanType ) throws Throwable {
93- ParameterNameProvider classInfo = req .require (ParameterNameProvider .class );
94- List <Constructor <?>> constructors = Arrays .asList (beanType .getDeclaredConstructors ()).stream ()
95- .filter (c -> c .isAnnotationPresent (Inject .class ))
96- .collect (Collectors .toList ());
97- if (constructors .size () == 0 ) {
98- // No inject annotation, use a declared constructor
99- constructors .addAll (Arrays .asList (beanType .getDeclaredConstructors ()));
100- }
101- if (constructors .size () > 1 ) {
102- return null ;
103- }
104- Constructor <?> constructor = constructors .get (0 );
105- RequestParamProvider provider = new RequestParamProviderImpl (
106- new RequestParamNameProviderImpl (classInfo ));
107- List <RequestParam > parameters = provider .parameters (constructor );
108- Object [] args = new Object [parameters .size ()];
109- for (int i = 0 ; i < args .length ; i ++) {
110- args [i ] = value (parameters .get (i ), req , rsp );
111- }
112- // inject args
113- final Object bean = constructor .newInstance (args );
114-
115- // inject fields
116- for (Entry <String , Mutant > param : params .entrySet ()) {
117- String pname = param .getKey ();
118- try {
119- List <String > path = name (pname );
120- Object root = seek (bean , path );
121- String fname = path .get (path .size () - 1 );
122-
123- Field field = root .getClass ().getDeclaredField (fname );
124- int mods = field .getModifiers ();
125- if (!Modifier .isFinal (mods ) && !Modifier .isStatic (mods ) && !Modifier .isTransient (mods )) {
126- // get
127- Object value = value (new RequestParam (field , pname , field .getGenericType ()), req , rsp );
128- // set
129- field .setAccessible (true );
130- field .set (root , value );
131- }
132- } catch (NoSuchFieldException ex ) {
133- LoggerFactory .getLogger (Request .class ).debug ("No matching field for: {}" , pname );
134- }
83+ BeanPlan plan = beans .get (beanType );
84+ if (plan == null ) {
85+ ParameterNameProvider classInfo = req .require (ParameterNameProvider .class );
86+ plan = new BeanPlan (classInfo , beanType );
87+ beans .put (beanType , plan );
13588 }
136- return bean ;
137- }
138-
139- /**
140- * Given a path like: <code>profile[address][country][name]</code> this method will traverse the
141- * path and seek the object in [country].
142- *
143- * @param bean Root bean.
144- * @param path Path to traverse.
145- * @return The last object in the path.
146- * @throws Exception If something goes wrong.
147- */
148- private Object seek (final Object bean , final List <String > path ) throws Exception {
149- Object it = bean ;
150- for (int i = 0 ; i < path .size () - 1 ; i ++) {
151- Field field = it .getClass ().getDeclaredField (path .get (i ));
152- field .setAccessible (true );
153-
154- Object next = field .get (it );
155- if (next == null ) {
156- next = field .getType ().newInstance ();
157- field .set (it , next );
158- }
159- it = next ;
160- }
161- return it ;
89+ return plan .newBean (p -> value (p , req , rsp ), params );
16290 }
16391
16492 private Object newBeanInterface (final Request req , final Response rsp , final Class <?> beanType ) {
@@ -179,12 +107,4 @@ private Object value(final RequestParam param, final Request req, final Response
179107 .getOrElseThrow (Function .identity ());
180108 }
181109
182- private static List <String > name (final String name ) {
183- return Splitter .on (new CharMatcher () {
184- @ Override
185- public boolean matches (final char c ) {
186- return c == '[' || c == ']' ;
187- }
188- }).trimResults ().omitEmptyStrings ().splitToList (name );
189- }
190110}
0 commit comments