88import java .lang .reflect .Method ;
99import java .lang .reflect .Modifier ;
1010import java .util .*;
11+ import java .util .stream .Collectors ;
1112
1213public class CatBaseInjector implements Injector {
1314 private final List <Dependency <?>> dependencies ;
@@ -79,7 +80,7 @@ public void destroy() {
7980 @ SuppressWarnings ("unchecked" )
8081 public <T > T createInstance (Class <T > clazz ) {
8182 if (clazz .isEnum ()) {
82- throw new InjectorException ("Enums are not allowed " );
83+ throw new InjectorException ("Enums (" + clazz . getSimpleName () + ") are not supported " );
8384 }
8485
8586 if (clazz .isInterface () || Modifier .isAbstract (clazz .getModifiers ())) {
@@ -88,7 +89,7 @@ public <T> T createInstance(Class<T> clazz) {
8889 .map (classElement -> (Class <? extends T >) classElement )
8990 .toList ();
9091 if (implementations .isEmpty ()) {
91- throw new InjectorException ("No implementation found for abstraction layer " + clazz .getName ());
92+ throw new InjectorException ("No matching dependency implementation for " + clazz .getName ());
9293 }
9394
9495 //if only one implementation - no need to look for primary implementations
@@ -99,10 +100,10 @@ public <T> T createInstance(Class<T> clazz) {
99100 .filter (injectable -> injectable .isAnnotationPresent (Primary .class ))
100101 .toList ();
101102 if (primaryImplementations .size () > 1 ) {
102- throw new InjectorException ("More than one primary implementation of abstraction layer " + clazz .getName ());
103+ throw new InjectorException ("More than one primary implementation for dependency " + clazz .getName ());
103104 }
104105 if (primaryImplementations .isEmpty ()) {
105- throw new InjectorException ("No primary implementation found for abstraction layer " + clazz .getName ());
106+ throw new InjectorException ("No primary implementation found for dependency " + clazz .getName ());
106107 }
107108 return createInstance (primaryImplementations .get (0 ));
108109 }
@@ -149,24 +150,46 @@ public void registerInjectables() {
149150 .filter (method -> method .isAnnotationPresent (Provide .class ))
150151 .forEach (method -> {
151152 if (method .getReturnType ().isAnnotationPresent (Component .class )) {
152- throw new InjectorException ("Provide methods annotated with @Component are not allowed" );
153+ throw new InjectorException ("Provide method types annotated with @Component are not allowed" );
153154 }
154155 registerDependency (method .getReturnType (), method , clazz );
155156 });
156157 });
157158
159+ //check for nested dependencies for provided instances
160+ dependencies .stream ().filter (dependency -> dependency .getProvideMethod () != null ).forEach (dependency -> {
161+ Dependency <?> containingDependency = dependencies .stream ()
162+ .filter (containingClass -> containingClass .getClazz ().equals (dependency .getContainingClass ()))
163+ .findFirst ()
164+ .orElseThrow (() -> new InjectorException ("Dependency " + dependency .getContainingClass ().getName () + " not found" ));
165+ dependency .addDependency (containingDependency );
166+ });
158167 //check for nested dependencies
159168 dependencies .stream ()
160169 // filter only dependencies that are needed for the dependency tree
161170 .filter (dependency -> dependency .getProvideMethod () == null && dependency .getInstance () == null )
162- .forEach (dependency -> Arrays .stream (dependency .getClazz ().getDeclaredFields ())
163- .filter (field -> !field .isAnnotationPresent (Exclude .class ))
164- .forEach (field -> {
165- Class <?> fieldType = field .getType ();
166- dependencies .stream ()
167- .filter (dep -> fieldType .isAssignableFrom (dep .getClazz ()))
168- .findFirst ().ifPresent (dependency ::addDependency );
169- }));
171+ .forEach (dependency -> {
172+ Constructor <?> validConstructor = getConstructor (dependency );
173+ Arrays .stream (validConstructor .getParameterTypes ())
174+ .forEach (type -> {
175+ dependencies .stream ()
176+ .filter (dep -> type .isAssignableFrom (dep .getClazz ()))
177+ .findFirst ()
178+ .ifPresent (dependency ::addDependency );
179+ });
180+
181+
182+
183+ Arrays .stream (dependency .getClazz ().getDeclaredFields ())
184+ .filter (field -> field .isAnnotationPresent (Inject .class ))
185+ .forEach (field -> {
186+ Class <?> fieldType = field .getType ();
187+ dependencies .stream ()
188+ .filter (dep -> fieldType .isAssignableFrom (dep .getClazz ()))
189+ .findFirst ()
190+ .ifPresent (dependency ::addDependency );
191+ });
192+ });
170193
171194 //check for circular dependencies
172195 Set <Dependency <?>> visited = new HashSet <>();
@@ -207,6 +230,19 @@ public void registerInjectables() {
207230 });
208231 }
209232
233+ @ SuppressWarnings ("unchecked" )
234+ private <T > Constructor <T > getConstructor (Dependency <T > dependency ) {
235+ return (Constructor <T >) Arrays .stream (dependency .getClazz ().getConstructors ())
236+ .filter (constructorElement -> constructorElement .getParameterTypes ().length == 0
237+ || Arrays .stream (constructorElement .getParameterTypes ())
238+ .allMatch (element ->
239+ containsByClass (element ) || (element .isInterface () && element .isAnnotationPresent (Component .class ))
240+ )
241+ )
242+ .findAny ()
243+ .orElseThrow (() -> new InjectorException ("No constructors with valid parameters found for " + dependency .getClazz ().getName ()));
244+ }
245+
210246 private void initializeDependency (Dependency <?> dependency , List <Dependency <?>> sortedDependencies ) {
211247 if (!sortedDependencies .contains (dependency )) {
212248 dependency .getDepends ().forEach (dep -> initializeDependency (dep , sortedDependencies ));
@@ -216,7 +252,7 @@ private void initializeDependency(Dependency<?> dependency, List<Dependency<?>>
216252
217253 private void checkCircularDependency (Dependency <?> dependency , Set <Dependency <?>> visited , Set <Dependency <?>> stack ) {
218254 if (stack .contains (dependency )) {
219- throw new InjectorException ("Circular dependency detected involving " + dependency . getClazz ( ));
255+ throw new InjectorException ("Circular dependency detected involving " + stack . stream (). map ( clazz -> clazz . getClass (). getSimpleName ()). collect ( Collectors . joining () ));
220256 }
221257 if (!visited .contains (dependency )) {
222258 visited .add (dependency );
@@ -245,15 +281,15 @@ public <T> void injectField(T instance) {
245281 return dependency .getClazz ().equals (field .getType ()) || (!injectName .isEmpty () && injectName .equals (dependency .getName ()));
246282 })
247283 .findFirst ()
248- .orElseThrow (() -> new InjectorException ("Could not find injectable for field " + field .getName ()))
284+ .orElseThrow (() -> new InjectorException ("Could not find dependency of type + " + field . getType (). getSimpleName () + " for field " + instance . getClass (). getSimpleName () + ". " + field .getName ()))
249285 .getInstance ();
250286 if (toInject .getClass ().equals (instance .getClass ())) {
251- throw new InjectorException ("Could not inject field " + field .getName () + " into itself" );
287+ throw new InjectorException ("Could not inject field " + instance . getClass (). getSimpleName () + "." + field .getName () + " into itself" );
252288 }
253289 field .setAccessible (true );
254290 field .set (instance , toInject );
255291 } catch (IllegalAccessException e ) {
256- throw new InjectorException ("Could not inject field " + field .getName (), e );
292+ throw new InjectorException ("Could not inject field " + instance . getClass (). getSimpleName () + "." + field .getName (), e );
257293 }
258294 });
259295 }
@@ -299,7 +335,7 @@ public <T> T getInstance(Class<T> clazz) {
299335 return ((T ) dependencies .stream ()
300336 .filter (dependency -> dependency .getClazz ().equals (clazz ))
301337 .findFirst ()
302- .orElseThrow (() -> new InjectorException ("Could not find injectable for class " + clazz .getName ()))
338+ .orElseThrow (() -> new InjectorException ("Could not find dependency of type " + clazz .getName ()))
303339 .getInstance ());
304340 }
305341
@@ -308,29 +344,23 @@ public void clearInjectables() {
308344 this .dependencies .clear ();
309345 }
310346
311-
312- @ SuppressWarnings ("unchecked" )
313347 private <T > T instantiate (Dependency <T > dependency ) {
314- Constructor <T > constructor = (Constructor <T >) Arrays .stream (dependency .getClazz ().getConstructors ())
315- .filter (constructorElement -> constructorElement .getParameterTypes ().length == 0 || Arrays .stream (constructorElement .getParameterTypes ())
316- .allMatch (element -> containsByClass (element ) || (element .isInterface () && element .isAnnotationPresent (Component .class )))
317- )
318- .findAny ()
319- .orElseThrow (() -> new InjectorException ("No matching constructor found for class " + dependency .getClazz ().getName ()));
348+ Constructor <T > constructor = getConstructor (dependency );
349+
320350 try {
321351 if (constructor .getParameterCount () == 0 ) {
322352 return constructor .newInstance ();
323353 }
324354
325355 return constructor .newInstance (Arrays .stream (constructor .getParameterTypes ()).map (this ::getInstance ).toArray ());
326356 } catch (Exception exception ) {
327- throw new InjectorException ("Could not instantiate class " + dependency .getClazz ().getName (), exception );
357+ throw new InjectorException ("Could not create instance of class " + dependency .getClazz ().getName (), exception );
328358 }
329359 }
330360
331361 private <T > void registerDependency (Class <T > clazz , Method provideMethod , Class <?> containingClass ) {
332362 if (this .containsByClass (clazz )) {
333- throw new InjectorException ("Class " + clazz .getName () + " is already registered" );
363+ throw new InjectorException ("Duplicate dependency for type " + clazz .getName ());
334364 }
335365
336366 String injectableName ;
@@ -342,7 +372,7 @@ private <T> void registerDependency(Class<T> clazz, Method provideMethod, Class<
342372 }
343373
344374 if (containsByName (injectableName )) {
345- throw new InjectorException ("Class with component name " + injectableName + " is already registered" );
375+ throw new InjectorException ("Duplicate component name " + injectableName );
346376 }
347377 Dependency <T > dependency = new Dependency <>(injectableName , clazz , null , provideMethod , containingClass );
348378 this .dependencies .add (dependency );
0 commit comments