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