001 /*****************************************************************************
002 * Copyright (C) PicoContainer Organization. All rights reserved. *
003 * ------------------------------------------------------------------------- *
004 * The software in this package is published under the terms of the BSD *
005 * style license a copy of which has been included with this distribution in *
006 * the LICENSE.txt file. *
007 * *
008 * Original code by *
009 *****************************************************************************/
010 package org.picocontainer.tck;
011
012 import static org.junit.Assert.assertEquals;
013 import static org.junit.Assert.assertFalse;
014 import static org.junit.Assert.assertNotNull;
015 import static org.junit.Assert.assertNotSame;
016 import static org.junit.Assert.assertNull;
017 import static org.junit.Assert.assertSame;
018 import static org.junit.Assert.assertTrue;
019 import static org.junit.Assert.fail;
020
021 import org.picocontainer.injectors.IterativeInjector;
022 import org.picocontainer.parameters.ComponentParameter;
023
024 import org.picocontainer.Converting;
025
026 import java.io.ByteArrayInputStream;
027 import java.io.ByteArrayOutputStream;
028 import java.io.IOException;
029 import java.io.ObjectInputStream;
030 import java.io.ObjectOutputStream;
031 import java.io.Serializable;
032 import java.lang.reflect.Type;
033 import java.util.ArrayList;
034 import java.util.Arrays;
035 import java.util.Collection;
036 import java.util.HashMap;
037 import java.util.HashSet;
038 import java.util.Iterator;
039 import java.util.LinkedList;
040 import java.util.List;
041 import java.util.Map;
042 import java.util.Properties;
043 import java.util.Set;
044 import org.junit.Test;
045 import org.picocontainer.Behavior;
046 import org.picocontainer.Characteristics;
047 import org.picocontainer.ComponentAdapter;
048 import org.picocontainer.ComponentFactory;
049 import org.picocontainer.DefaultPicoContainer;
050 import org.picocontainer.Disposable;
051 import org.picocontainer.MutablePicoContainer;
052 import org.picocontainer.NameBinding;
053 import org.picocontainer.Parameter;
054 import org.picocontainer.PicoCompositionException;
055 import org.picocontainer.PicoContainer;
056 import org.picocontainer.PicoException;
057 import org.picocontainer.PicoVerificationException;
058 import org.picocontainer.PicoVisitor;
059 import org.picocontainer.Startable;
060 import org.picocontainer.adapters.InstanceAdapter;
061 import org.picocontainer.behaviors.AbstractBehavior;
062 import org.picocontainer.behaviors.AdaptingBehavior;
063 import org.picocontainer.injectors.AbstractInjector;
064 import org.picocontainer.injectors.ConstructorInjector;
065 import org.picocontainer.injectors.AbstractInjector.UnsatisfiableDependenciesException;
066 import org.picocontainer.injectors.SingleMemberInjector.ParameterCannotBeNullException;
067 import org.picocontainer.lifecycle.NullLifecycleStrategy;
068 import org.picocontainer.monitors.NullComponentMonitor;
069 import org.picocontainer.parameters.BasicComponentParameter;
070 import org.picocontainer.parameters.ConstantParameter;
071 import org.picocontainer.parameters.NullParameter;
072 import org.picocontainer.testmodel.DependsOnTouchable;
073 import org.picocontainer.testmodel.SimpleTouchable;
074 import org.picocontainer.testmodel.Touchable;
075 import org.picocontainer.testmodel.Washable;
076 import org.picocontainer.testmodel.WashableTouchable;
077 import org.picocontainer.visitors.AbstractPicoVisitor;
078 import org.picocontainer.visitors.TraversalCheckingVisitor;
079 import org.picocontainer.visitors.VerifyingVisitor;
080
081 /** This test tests (at least it should) all the methods in MutablePicoContainer. */
082 @SuppressWarnings("serial")
083 public abstract class AbstractPicoContainerTest {
084
085 protected abstract MutablePicoContainer createPicoContainer(PicoContainer parent);
086
087 protected final MutablePicoContainer createPicoContainerWithDependsOnTouchableOnly() throws PicoCompositionException {
088 MutablePicoContainer pico = createPicoContainer(null);
089 pico.addComponent(DependsOnTouchable.class);
090 return pico;
091 }
092
093 protected final MutablePicoContainer createPicoContainerWithTouchableAndDependsOnTouchable() throws PicoCompositionException {
094 MutablePicoContainer pico = createPicoContainerWithDependsOnTouchableOnly();
095 pico.as(Characteristics.CACHE).addComponent(Touchable.class, SimpleTouchable.class);
096 return pico;
097 }
098
099 @Test public void testBasicInstantiationAndContainment() throws PicoException {
100 PicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable();
101 assertTrue("Component should be instance of Touchable",
102 Touchable.class.isAssignableFrom(pico.getComponentAdapter(Touchable.class, (NameBinding) null).getComponentImplementation()));
103 }
104
105 @Test public void testRegisteredComponentsExistAndAreTheCorrectTypes() throws PicoException {
106 PicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable();
107 assertNotNull("Container should have Touchable addComponent",
108 pico.getComponentAdapter(Touchable.class, (NameBinding) null));
109 assertNotNull("Container should have DependsOnTouchable addComponent",
110 pico.getComponentAdapter(DependsOnTouchable.class, (NameBinding) null));
111 assertTrue("Component should be instance of Touchable",
112 pico.getComponent(Touchable.class) != null);
113 assertTrue("Component should be instance of DependsOnTouchable",
114 pico.getComponent(DependsOnTouchable.class) != null);
115 assertNull("should not have non existent addComponent", pico.getComponentAdapter(Map.class, (NameBinding) null));
116 }
117
118 @Test public void testRegistersSingleInstance() throws PicoException {
119 MutablePicoContainer pico = createPicoContainer(null);
120 StringBuffer sb = new StringBuffer();
121 pico.addComponent(sb);
122 assertSame(sb, pico.getComponent(StringBuffer.class));
123 }
124
125 @Test public void testContainerIsSerializable() throws PicoException,
126 IOException, ClassNotFoundException
127 {
128
129 getTouchableFromSerializedContainer();
130
131 }
132
133 private Touchable getTouchableFromSerializedContainer() throws IOException, ClassNotFoundException {
134 MutablePicoContainer pico = createPicoContainerWithTouchableAndDependsOnTouchable();
135 // Add a list too, using a constant parameter
136 pico.addComponent("list", ArrayList.class, new ConstantParameter(10));
137
138 ByteArrayOutputStream baos = new ByteArrayOutputStream();
139 ObjectOutputStream oos = new ObjectOutputStream(baos);
140
141 oos.writeObject(pico);
142 ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(baos.toByteArray()));
143
144 pico = (MutablePicoContainer)ois.readObject();
145
146 DependsOnTouchable dependsOnTouchable = pico.getComponent(DependsOnTouchable.class);
147 assertNotNull(dependsOnTouchable);
148 return pico.getComponent(Touchable.class);
149 }
150
151 @Test public void testSerializedContainerCanRetrieveImplementation() throws PicoException,
152 IOException, ClassNotFoundException
153 {
154
155 Touchable touchable = getTouchableFromSerializedContainer();
156
157 SimpleTouchable simpleTouchable = (SimpleTouchable)touchable;
158
159 assertTrue(simpleTouchable.wasTouched);
160 }
161
162
163 @Test public void testGettingComponentWithMissingDependencyFails() throws PicoException {
164 MutablePicoContainer picoContainer = (MutablePicoContainer) createPicoContainerWithDependsOnTouchableOnly();
165 picoContainer.setName("parent");
166 try {
167 picoContainer.getComponent(DependsOnTouchable.class);
168 fail("should need a Touchable");
169 } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
170 String message = e.getMessage().replace("org.picocontainer.testmodel.", "");
171 assertEquals("DependsOnTouchable has unsatisfied dependency 'interface Touchable' for constructor 'public DependsOnTouchable(Touchable)' from parent:1<|", message);
172
173 }
174 }
175
176 @Test public void testDuplicateRegistration() {
177 try {
178 MutablePicoContainer pico = createPicoContainer(null);
179 pico.addComponent(Object.class);
180 pico.addComponent(Object.class);
181 fail("Should have failed with duplicate registration");
182 } catch (PicoCompositionException e) {
183 assertTrue("Wrong key", e.getMessage().indexOf(Object.class.toString()) > -1);
184 }
185 }
186
187 @Test public void testExternallyInstantiatedObjectsCanBeRegisteredAndLookedUp() throws PicoException {
188 MutablePicoContainer pico = createPicoContainer(null);
189 final HashMap map = new HashMap();
190 pico.as(getProperties()).addComponent(Map.class, map);
191 assertSame(map, pico.getComponent(Map.class));
192 }
193
194 @Test public void testAmbiguousResolution() throws PicoCompositionException {
195 MutablePicoContainer pico = createPicoContainer(null);
196 pico.addComponent("ping", String.class);
197 pico.addComponent("pong", "pang");
198 try {
199 pico.getComponent(String.class);
200 } catch (AbstractInjector.AmbiguousComponentResolutionException e) {
201 assertTrue(e.getMessage().indexOf("java.lang.String") != -1);
202 assertTrue(e.getMessage().indexOf("<no-component>") != -1);
203 assertTrue(e.getMessage().indexOf("<unknown>") != -1);
204 }
205 }
206
207 @Test public void testLookupWithUnregisteredKeyReturnsNull() throws PicoCompositionException {
208 MutablePicoContainer pico = createPicoContainer(null);
209 assertNull(pico.getComponent(String.class));
210 }
211
212 @Test public void testLookupWithUnregisteredTypeReturnsNull() throws PicoCompositionException {
213 MutablePicoContainer pico = createPicoContainer(null);
214 assertNull(pico.getComponent(String.class));
215 }
216
217 public static class ListAdder {
218 public ListAdder(Collection<String> list) {
219 list.add("something");
220 }
221 }
222
223 @Test public void testUnsatisfiableDependenciesExceptionGivesVerboseEnoughErrorMessage() {
224 MutablePicoContainer pico = createPicoContainer(null);
225 pico.setName("parent");
226 pico.addComponent(ComponentD.class);
227
228 try {
229 pico.getComponent(ComponentD.class);
230 } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
231 String msg = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", "");
232 assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:1<|", msg);
233 }
234 }
235
236 @Test public void testUnsatisfiableDependenciesExceptionGivesUnsatisfiedDependencyTypes() {
237 MutablePicoContainer pico = (MutablePicoContainer) createPicoContainer(null);
238 pico.setName("parent");
239 // D depends on E and B
240 pico.addComponent(ComponentD.class);
241
242 // first - do not register any dependency
243 // should yield first unsatisfied dependency
244 try {
245 pico.getComponent(ComponentD.class);
246 } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
247
248 String message = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", "");
249 assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:1<|", message);
250 }
251
252 // now register only first dependency
253 // should yield second unsatisfied dependency
254 pico.addComponent(ComponentE.class);
255 try {
256 pico.getComponent(ComponentD.class);
257 } catch (AbstractInjector.UnsatisfiableDependenciesException e) {
258 String message = e.getMessage().replace("org.picocontainer.tck.AbstractPicoContainerTest$Component", "");
259 assertEquals("D has unsatisfied dependency 'class B' for constructor 'public D(E,B)' from parent:2<|", message);
260 }
261 }
262
263 @Test public void testCyclicDependencyThrowsCyclicDependencyException() {
264 assertCyclicDependencyThrowsCyclicDependencyException(createPicoContainer(null));
265 }
266
267 private static void assertCyclicDependencyThrowsCyclicDependencyException(MutablePicoContainer pico) {
268 pico.addComponent(ComponentB.class);
269 pico.addComponent(ComponentD.class);
270 pico.addComponent(ComponentE.class);
271
272 try {
273 pico.getComponent(ComponentD.class);
274 fail("CyclicDependencyException expected");
275 } catch (AbstractInjector.CyclicDependencyException e) {
276 // CyclicDependencyException reports now the stack.
277 //final List dependencies = Arrays.asList(ComponentD.class.getConstructors()[0].getParameterTypes());
278 final List<Class> dependencies = Arrays.<Class>asList(ComponentD.class, ComponentE.class, ComponentD.class);
279 final List<Class> reportedDependencies = Arrays.asList(e.getDependencies());
280 assertEquals(dependencies, reportedDependencies);
281 } catch (StackOverflowError e) {
282 fail();
283 }
284 }
285
286 @Test public void testCyclicDependencyThrowsCyclicDependencyExceptionWithParentContainer() {
287 MutablePicoContainer pico = createPicoContainer(createPicoContainer(null));
288 assertCyclicDependencyThrowsCyclicDependencyException(pico);
289 }
290
291 @Test public void testRemovalNonRegisteredComponentAdapterWorksAndReturnsNull() {
292 final MutablePicoContainer picoContainer = createPicoContainer(null);
293 assertNull(picoContainer.removeComponent("COMPONENT DOES NOT EXIST"));
294 }
295
296 /** Important! Nanning really, really depends on this! */
297 @Test public void testComponentAdapterRegistrationOrderIsMaintained() throws NoSuchMethodException {
298
299 ConstructorInjector c1 = new ConstructorInjector("1", Object.class, null, new NullComponentMonitor(), false);
300 ConstructorInjector c2 = new ConstructorInjector("2", String.class, null, new NullComponentMonitor(), false);
301
302 MutablePicoContainer picoContainer = createPicoContainer(null);
303 picoContainer.addAdapter(c1).addAdapter(c2);
304 Collection<ComponentAdapter<?>> list2 = picoContainer.getComponentAdapters();
305 //registration order should be maintained
306 assertEquals(2, list2.size());
307 assertEquals(c1.getComponentKey(), ((ComponentAdapter)list2.toArray()[0]).getComponentKey());
308 assertEquals(c2.getComponentKey(), ((ComponentAdapter)list2.toArray()[1]).getComponentKey());
309
310 picoContainer.getComponents(); // create all the instances at once
311 assertFalse("instances should be created in same order as adapters are created",
312 picoContainer.getComponents().get(0) instanceof String);
313 assertTrue("instances should be created in same order as adapters are created",
314 picoContainer.getComponents().get(1) instanceof String);
315
316 MutablePicoContainer reversedPicoContainer = createPicoContainer(null);
317 reversedPicoContainer.addAdapter(c2);
318 reversedPicoContainer.addAdapter(c1);
319 //registration order should be maintained
320 list2 = reversedPicoContainer.getComponentAdapters();
321 assertEquals(2, list2.size());
322 assertEquals(c2.getComponentKey(), ((ComponentAdapter)list2.toArray()[0]).getComponentKey());
323 assertEquals(c1.getComponentKey(), ((ComponentAdapter)list2.toArray()[1]).getComponentKey());
324
325 reversedPicoContainer.getComponents(); // create all the instances at once
326 assertTrue("instances should be created in same order as adapters are created",
327 reversedPicoContainer.getComponents().get(0) instanceof String);
328 assertFalse("instances should be created in same order as adapters are created",
329 reversedPicoContainer.getComponents().get(1) instanceof String);
330 }
331
332 public static final class NeedsTouchable {
333 public final Touchable touchable;
334
335 public NeedsTouchable(Touchable touchable) {
336 this.touchable = touchable;
337 }
338 }
339
340 public static final class NeedsWashable {
341 public final Washable washable;
342
343 public NeedsWashable(Washable washable) {
344 this.washable = washable;
345 }
346 }
347
348 @Test public void testSameInstanceCanBeUsedAsDifferentTypeWhenCaching() {
349 MutablePicoContainer pico = createPicoContainer(null);
350 pico.as(Characteristics.CACHE).addComponent("wt", WashableTouchable.class);
351 pico.addComponent("nw", NeedsWashable.class);
352 pico.as(Characteristics.CACHE).addComponent("nt", NeedsTouchable.class);
353
354 NeedsWashable nw = (NeedsWashable)pico.getComponent("nw");
355 NeedsTouchable nt = (NeedsTouchable)pico.getComponent("nt");
356 assertSame(nw.washable, nt.touchable);
357 }
358
359 @Test public void testRegisterComponentWithObjectBadType() throws PicoCompositionException {
360 MutablePicoContainer pico = createPicoContainer(null);
361
362 try {
363 pico.addComponent(Serializable.class, new Object());
364 fail("Shouldn't be able to register an Object.class as Serializable because it is not, " +
365 "it does not implement it, Object.class does not implement much.");
366 } catch (ClassCastException e) {
367 assertNotNull(e.getMessage());
368 }
369
370 }
371
372 public static class JMSService {
373 public final String serverid;
374 public final String path;
375
376 public JMSService(String serverid, String path) {
377 this.serverid = serverid;
378 this.path = path;
379 }
380 }
381
382 // http://jira.codehaus.org/secure/ViewIssue.jspa?key=PICO-52
383 @Test public void testPico52() {
384 MutablePicoContainer pico = createPicoContainer(null);
385
386 pico.addComponent("foo", JMSService.class, new ConstantParameter("0"), new ConstantParameter("something"));
387 JMSService jms = (JMSService)pico.getComponent("foo");
388 assertEquals("0", jms.serverid);
389 assertEquals("something", jms.path);
390 }
391
392 public static class ComponentA {
393 public final ComponentC c;
394
395 public ComponentA(ComponentB b, ComponentC c) {
396 this.c = c;
397 assertNotNull(b);
398 assertNotNull(c);
399 }
400 }
401
402 public static class ComponentB {
403 //Does nothing.
404 }
405
406 public static class ComponentC {
407 //Does nothing.
408 }
409
410 public static class ComponentD {
411 public ComponentD(ComponentE e, ComponentB b) {
412 assertNotNull(e);
413 assertNotNull(b);
414 }
415 }
416
417 public static class ComponentE {
418 public ComponentE(ComponentD d) {
419 assertNotNull(d);
420 }
421 }
422
423 public static class ComponentF {
424 public ComponentF(ComponentA a) {
425 assertNotNull(a);
426 }
427 }
428
429 @Test public void testAggregatedVerificationException() {
430 MutablePicoContainer pico = createPicoContainer(null);
431 pico.addComponent(ComponentA.class);
432 pico.addComponent(ComponentE.class);
433 try {
434 new VerifyingVisitor().traverse(pico);
435 fail("we expect a PicoVerificationException");
436 } catch (PicoVerificationException e) {
437 List nested = e.getNestedExceptions();
438 assertEquals(2, nested.size());
439 assertTrue(-1 != e.getMessage().indexOf(ComponentA.class.getName()));
440 assertTrue(-1 != e.getMessage().indexOf(ComponentE.class.getName()));
441 }
442 }
443
444 // An adapter has no longer a hosting container.
445
446 // @Test public void testRegistrationOfAdapterSetsHostingContainerAsSelf() {
447 // final InstanceAdapter componentAdapter = new InstanceAdapter("", new Object());
448 // final MutablePicoContainer picoContainer = createPicoContainer(null);
449 // picoContainer.addAdapter(componentAdapter);
450 // assertSame(picoContainer, componentAdapter.getContainer());
451 // }
452
453 public static class ContainerDependency {
454 public ContainerDependency(PicoContainer container) {
455 assertNotNull(container);
456 }
457 }
458
459 // ImplicitPicoContainer injection is bad. It is an open door for hackers. Developers with
460 // special PicoContainer needs should specifically register() a comtainer they want components to
461 // be able to pick up on.
462
463 // @Test public void testImplicitPicoContainerInjection() {
464 // MutablePicoContainer pico = createPicoContainer(null);
465 // pico.addAdapter(ContainerDependency.class);
466 // ContainerDependency dep = (ContainerDependency) pico.getComponent(ContainerDependency.class);
467 // assertSame(pico, dep.pico);
468 // }
469
470 @Test public void testShouldReturnNullWhenUnregistereingUnmanagedComponent() {
471 final MutablePicoContainer pico = createPicoContainer(null);
472 assertNull(pico.removeComponentByInstance("yo"));
473 }
474
475 @Test public void testShouldReturnNullForComponentAdapterOfUnregisteredType() {
476 final MutablePicoContainer pico = createPicoContainer(null);
477 assertNull(pico.getComponent(List.class));
478 }
479
480 @Test public void testShouldReturnNonMutableParent() {
481 DefaultPicoContainer parent = new DefaultPicoContainer();
482 final MutablePicoContainer picoContainer = createPicoContainer(parent);
483 assertNotSame(parent, picoContainer.getParent());
484 assertFalse(picoContainer.getParent() instanceof MutablePicoContainer);
485 }
486
487 class Foo implements Startable, Disposable {
488 public boolean started;
489 public boolean stopped;
490 public boolean disposed;
491
492 public void start() {
493 started = true;
494 }
495
496 public void stop() {
497 stopped = true;
498 }
499
500 public void dispose() {
501 disposed = true;
502 }
503
504 }
505
506 @Test public void testContainerCascadesDefaultLifecycle() {
507 final MutablePicoContainer picoContainer = createPicoContainer(null);
508 Foo foo = new Foo();
509 picoContainer.addComponent(foo);
510 picoContainer.start();
511 assertEquals(true, foo.started);
512 picoContainer.stop();
513 assertEquals(true, foo.stopped);
514 picoContainer.dispose();
515 assertEquals(true, foo.disposed);
516 }
517
518 @Test public void testComponentInstancesFromParentsAreNotDirectlyAccessible2() {
519 final MutablePicoContainer a = createPicoContainer(null);
520 final MutablePicoContainer b = createPicoContainer(a);
521 final MutablePicoContainer c = createPicoContainer(b);
522
523 Object ao = new Object();
524 Object bo = new Object();
525 Object co = new Object();
526
527 a.addComponent("a", ao);
528 b.addComponent("b", bo);
529 c.addComponent("c", co);
530
531 assertEquals(1, a.getComponents().size());
532 assertEquals(1, b.getComponents().size());
533 assertEquals(1, c.getComponents().size());
534 }
535
536 @Test public void testStartStopAndDisposeCascadedtoChildren() {
537 final MutablePicoContainer parent = createPicoContainer(null);
538 parent.addComponent(new StringBuffer());
539 final MutablePicoContainer child = createPicoContainer(parent);
540 parent.addChildContainer(child);
541 child.addComponent(LifeCycleMonitoring.class);
542 parent.start();
543 try {
544 child.start();
545 fail("IllegalStateException expected");
546 } catch (IllegalStateException e) {
547 assertEquals("child already started", "Cannot start. Current container state was: STARTED", e.getMessage());
548 }
549 parent.stop();
550 try {
551 child.stop();
552 fail("IllegalStateException expected");
553 } catch (IllegalStateException e) {
554 assertEquals("child not started", "Cannot stop. Current container state was: STOPPED", e.getMessage());
555 }
556 parent.dispose();
557 try {
558 child.dispose();
559 fail("IllegalStateException expected");
560 } catch (IllegalStateException e) {
561 assertEquals("child already disposed", "Cannot dispose. Current lifecycle state is: DISPOSED", e.getMessage());
562 }
563
564 }
565
566 @Test public void testMakingOfChildContainer() {
567 final MutablePicoContainer parent = createPicoContainer(null);
568 MutablePicoContainer child = parent.makeChildContainer();
569 assertNotNull(child);
570 }
571
572 @Test public void testMakingOfChildContainerPercolatesLifecycleManager() {
573 final MutablePicoContainer parent = createPicoContainer(null);
574 parent.addComponent("one", TestLifecycleComponent.class);
575 MutablePicoContainer child = parent.makeChildContainer();
576 assertNotNull(child);
577 child.addComponent("two", TestLifecycleComponent.class);
578 parent.start();
579 try {
580 child.start();
581 } catch (IllegalStateException e) {
582 assertEquals("child already started", "Cannot start. Current container state was: STARTED", e.getMessage());
583 }
584 //TODO - The Behavior reference in child containers is not used. Thus is is almost pointless
585 // The reason is because DefaultPicoContainer's accept() method visits child containers' on its own.
586 // This may be file for visiting components in a tree for general cases, but for lifecycle, we
587 // should hand to each Behavior's start(..) at each appropriate node. See mail-list discussion.
588 }
589
590 @SuppressWarnings("unused")
591 public static final class TestBehavior extends AbstractBehavior implements Behavior {
592
593 public final ArrayList<PicoContainer> started = new ArrayList<PicoContainer>();
594
595 public TestBehavior(ComponentAdapter delegate) {
596 super(delegate);
597 }
598
599 @Override
600 public void start(PicoContainer node) {
601 started.add(node);
602 }
603
604 @Override
605 public void stop(PicoContainer node) {
606 //Does nothing.
607 }
608
609 @Override
610 public void dispose(PicoContainer node) {
611 //Does nothing.
612 }
613
614 @Override
615 public boolean componentHasLifecycle() {
616 return true;
617 }
618
619 public String getDescriptor() {
620 return null;
621 }
622 }
623
624 public static class TestLifecycleComponent implements Startable {
625 public boolean started;
626
627 public void start() {
628 started = true;
629 }
630
631 public void stop() {
632 //Does nothing.
633 }
634 }
635
636 @Test public void testStartStopAndDisposeNotCascadedtoRemovedChildren() {
637 final MutablePicoContainer parent = createPicoContainer(null);
638 parent.addComponent(new StringBuffer());
639 StringBuffer sb = parent.getComponents(StringBuffer.class).get(0);
640
641 final MutablePicoContainer child = createPicoContainer(parent);
642 assertEquals(parent, parent.addChildContainer(child));
643 child.addComponent(LifeCycleMonitoring.class);
644 assertTrue(parent.removeChildContainer(child));
645 parent.start();
646 assertTrue(sb.toString().indexOf("-started") == -1);
647 parent.stop();
648 assertTrue(sb.toString().indexOf("-stopped") == -1);
649 parent.dispose();
650 assertTrue(sb.toString().indexOf("-disposed") == -1);
651 }
652
653 @Test public void testShouldCascadeStartStopAndDisposeToChild() {
654
655 StringBuffer sb = new StringBuffer();
656 final MutablePicoContainer parent = createPicoContainer(null);
657 parent.addComponent(sb);
658 parent.addComponent(Map.class, HashMap.class);
659
660 final MutablePicoContainer child = parent.makeChildContainer();
661 child.addComponent(LifeCycleMonitoring.class);
662
663 Map map = parent.getComponent(Map.class);
664 assertNotNull(map);
665 parent.start();
666 try {
667 child.start();
668 fail("IllegalStateException expected");
669 } catch (IllegalStateException e) {
670 assertEquals("child already started", "Cannot start. Current container state was: STARTED", e.getMessage());
671 }
672 parent.stop();
673 try {
674 child.stop();
675 fail("IllegalStateException expected");
676 } catch (IllegalStateException e) {
677 assertEquals("child not started", "Cannot stop. Current container state was: STOPPED", e.getMessage());
678 }
679 parent.dispose();
680 try {
681 child.dispose();
682 fail("IllegalStateException expected");
683 } catch (IllegalStateException e) {
684 assertEquals("child already disposed", "Cannot dispose. Current lifecycle state is: DISPOSED", e.getMessage());
685 }
686 }
687
688 public static final class LifeCycleMonitoring implements Startable, Disposable {
689 final StringBuffer sb;
690
691 public LifeCycleMonitoring(StringBuffer sb) {
692 this.sb = sb;
693 sb.append("-instantiated");
694 }
695
696 public void start() {
697 sb.append("-started");
698 }
699
700 public void stop() {
701 sb.append("-stopped");
702 }
703
704 public void dispose() {
705 sb.append("-disposed");
706 }
707 }
708
709 public static class RecordingStrategyVisitor extends AbstractPicoVisitor {
710
711 private final List<Object> list;
712
713 public RecordingStrategyVisitor(List<Object> list) {
714 this.list = list;
715 }
716
717 public boolean visitContainer(PicoContainer pico) {
718 list.add(pico.getClass());
719 return CONTINUE_TRAVERSAL;
720 }
721
722 public void visitComponentAdapter(ComponentAdapter componentAdapter) {
723 list.add(componentAdapter.getClass());
724 }
725
726 public void visitComponentFactory(ComponentFactory componentFactory) {
727 list.add(componentFactory.getClass());
728 }
729
730 public void visitParameter(Parameter parameter) {
731 list.add(parameter.getClass());
732 }
733
734 }
735
736 protected abstract Properties[] getProperties();
737
738 @Test public void testAcceptImplementsBreadthFirstStrategy() {
739 final MutablePicoContainer parent = createPicoContainer(null);
740 final MutablePicoContainer child = parent.makeChildContainer();
741 ComponentAdapter hashMapAdapter =
742 parent.as(getProperties()).addAdapter(new ConstructorInjector(HashMap.class, HashMap.class, null, new NullComponentMonitor(), false))
743 .getComponentAdapter(HashMap.class, (NameBinding) null);
744 ComponentAdapter hashSetAdapter =
745 parent.as(getProperties()).addAdapter(new ConstructorInjector(HashSet.class, HashSet.class, null, new NullComponentMonitor(), false))
746 .getComponentAdapter(HashSet.class, (NameBinding) null);
747 InstanceAdapter instanceAdapter = new InstanceAdapter(String.class, "foo",
748 new NullLifecycleStrategy(),
749 new NullComponentMonitor());
750 ComponentAdapter stringAdapter = parent.as(getProperties()).addAdapter(instanceAdapter).getComponentAdapter(instanceAdapter.getComponentKey());
751 ComponentAdapter arrayListAdapter =
752 child.as(getProperties()).addAdapter(new ConstructorInjector(ArrayList.class, ArrayList.class, null, new NullComponentMonitor(), false))
753 .getComponentAdapter(ArrayList.class, (NameBinding) null);
754 Parameter componentParameter = BasicComponentParameter.BASIC_DEFAULT;
755 Parameter throwableParameter = new ConstantParameter(new Throwable("bar"));
756 ConstructorInjector ci = new ConstructorInjector(Exception.class, Exception.class, new Parameter[] {componentParameter,
757 throwableParameter}, new NullComponentMonitor(), false);
758 ComponentAdapter exceptionAdapter = child.as(getProperties()).addAdapter(ci).getComponentAdapter(Exception.class, (NameBinding) null);
759
760 List<Class> expectedList = new ArrayList<Class>();
761
762 addContainers(expectedList);
763 addDefaultComponentFactories(expectedList);
764 expectedList.add(hashMapAdapter.getClass());
765 expectedList.add(hashSetAdapter.getClass());
766 expectedList.add(stringAdapter.getClass());
767 addContainers(expectedList);
768 addDefaultComponentFactories(expectedList);
769 expectedList.add(arrayListAdapter.getClass());
770 expectedList.add(exceptionAdapter.getClass());
771 expectedList.add(componentParameter.getClass());
772 expectedList.add(throwableParameter.getClass());
773 List<Object> visitedList = new LinkedList<Object>();
774 PicoVisitor visitor = new RecordingStrategyVisitor(visitedList);
775 visitor.traverse(parent);
776 assertEquals(expectedList.size(), visitedList.size());
777 for (Class c : expectedList) {
778 assertTrue(visitedList.remove(c));
779 }
780 assertEquals(0, visitedList.size());
781 }
782
783 /**
784 * Verifies that you can halt a container traversal.
785 */
786 @Test
787 public void testAcceptIsAbortable() {
788 final MutablePicoContainer parent = createPicoContainer(null);
789 final MutablePicoContainer child = parent.makeChildContainer();
790 child.addComponent("This is a test");
791
792 TraversalCheckingVisitor parentComponentCountingVisitor = new TraversalCheckingVisitor() {
793 private int containerCount = 0;
794
795 @Override
796 @SuppressWarnings("unused")
797 public void visitComponentAdapter(ComponentAdapter<?> componentAdapter) {
798 if (containerCount == 0) {
799 fail("Should have visited a container first");
800 }
801 fail("Should never have visited an adapter.");
802 }
803
804 @Override
805 @SuppressWarnings("unused")
806 public boolean visitContainer(PicoContainer pico) {
807 containerCount++;
808 if (containerCount > 1) {
809 return ABORT_TRAVERSAL;
810 }
811
812 return CONTINUE_TRAVERSAL;
813 }
814
815 };
816
817 parentComponentCountingVisitor.traverse(parent);
818 }
819
820 protected void addContainers(List expectedList) {
821 expectedList.add(DefaultPicoContainer.class);
822 }
823
824 protected void addDefaultComponentFactories(List expectedList) {
825 expectedList.add(AdaptingBehavior.class);
826 }
827
828 @Test public void testAmbiguousDependencies() throws PicoCompositionException {
829
830 MutablePicoContainer pico = this.createPicoContainer(null);
831
832 // Register two Touchables that Fred will be confused about
833 pico.addComponent(SimpleTouchable.class);
834 pico.addComponent(DerivedTouchable.class);
835
836 // Register a confused DependsOnTouchable
837 pico.addComponent(DependsOnTouchable.class);
838
839 try {
840 pico.getComponent(DependsOnTouchable.class);
841 fail("DependsOnTouchable should have been confused about the two Touchables");
842 } catch (AbstractInjector.AmbiguousComponentResolutionException e) {
843 List componentImplementations = Arrays.asList(e.getAmbiguousComponentKeys());
844 assertTrue(componentImplementations.contains(DerivedTouchable.class));
845 assertTrue(componentImplementations.contains(SimpleTouchable.class));
846
847 assertTrue(e.getMessage().indexOf(DerivedTouchable.class.getName()) != -1);
848 assertTrue(e.getMessage().indexOf("public org.picocontainer.testmodel.DependsOnTouchable(org.picocontainer.testmodel.Touchable)") != -1);
849 }
850 }
851
852
853 public static class DerivedTouchable extends SimpleTouchable {
854 public DerivedTouchable() {
855 //Does nothing.
856 }
857 }
858
859
860 public static final class NonGreedyClass {
861
862 public final int value = 0;
863
864 public NonGreedyClass() {
865 //Do nothing.
866 }
867
868 public NonGreedyClass(ComponentA component) {
869 fail("Greedy Constructor should never have been called. Instead got: " + component);
870 }
871
872
873 }
874
875 @Test public void testNoArgConstructorToBeSelected() {
876 MutablePicoContainer pico = this.createPicoContainer(null);
877 pico.addComponent(ComponentA.class);
878 pico.addComponent(NonGreedyClass.class, NonGreedyClass.class, Parameter.ZERO);
879
880
881 NonGreedyClass instance = pico.getComponent(NonGreedyClass.class);
882 assertNotNull(instance);
883 }
884
885 public static class ConstantParameterTestService {
886 private final String arg;
887
888 public ConstantParameterTestService(String arg) {
889 this.arg = arg;
890 }
891
892 public String getArg() {
893 return arg;
894 }
895 }
896
897
898 /**
899 * Currently failing
900 */
901 @Test
902 public void testNullConstantParameter() {
903 MutablePicoContainer pico = createPicoContainer(null);
904 pico.addComponent(ConstantParameterTestService.class, ConstantParameterTestService.class, NullParameter.INSTANCE);
905 ConstantParameterTestService service = (ConstantParameterTestService) pico.getComponent(ConstantParameterTestService.class);
906 assertNotNull(service);
907 assertNull(service.getArg());
908 }
909
910
911 public static class PrimitiveConstructor {
912 @SuppressWarnings("unused")
913 public PrimitiveConstructor(int number) {
914 //does nothing.
915 }
916 }
917
918 @Test(expected=ParameterCannotBeNullException.class)
919 public void testNullConstantParametersDoNotInjectOnPrimitives() {
920 MutablePicoContainer pico = createPicoContainer(null);
921 pico.addComponent(PrimitiveConstructor.class, PrimitiveConstructor.class, NullParameter.INSTANCE);
922
923 //Should throw exception here.
924 pico.getComponent(PrimitiveConstructor.class);
925 }
926
927
928 @Test
929 public void testNullValuesDoNotInject() {
930 MutablePicoContainer pico = createPicoContainer(null);
931 pico.addComponent(ConstantParameterTestService.class, ConstantParameterTestService.class, new ConstantParameter(null));
932 try {
933 ConstantParameterTestService service = (ConstantParameterTestService) pico.getComponent(ConstantParameterTestService.class);
934 fail("Should have thrown unsatisfiable dependencies exception. Instead got " + service + " as a return value");
935 } catch (UnsatisfiableDependenciesException e) {
936 assertNotNull(e.getMessage());
937 }
938 }
939
940 @Test
941 public void testNullComponentsDoNotInject() {
942 MutablePicoContainer pico = createPicoContainer(null)
943 .addComponent(ComponentA.class)
944 .addComponent(ComponentB.class);
945
946
947 try {
948 pico.addComponent(ComponentC.class, null);
949 fail("Pico should not have been able to register null component instance");
950 } catch (NullPointerException e) {
951 assertNotNull(e.getMessage());
952 }
953 }
954
955
956 public static class ConverterSample {
957 public final int value;
958
959 public ConverterSample(Integer value) {
960 this.value = value;
961 }
962 }
963
964 @Test
965 public void testIntegrationWithConverters() {
966 MutablePicoContainer mpc = new DefaultPicoContainer();
967 if ( !(mpc instanceof Converting)) {
968 System.out.println("Skipping 'testIntegrationWithConverters' " +
969 "because pico implementation is not Converting");
970 return;
971 }
972
973 mpc.addComponent("converterParameter", "42")
974 .addComponent(ConverterSample.class, ConverterSample.class,
975 new ComponentParameter("converterParameter"));
976 ConverterSample result = mpc.getComponent(ConverterSample.class);
977 assertEquals(42, result.value);
978 }
979
980 }