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 Joerg Schaible                                           *
009 *****************************************************************************/
010package org.picocontainer.tck;
011
012import static org.junit.Assert.assertEquals;
013import static org.junit.Assert.assertFalse;
014import static org.junit.Assert.assertNotNull;
015import static org.junit.Assert.assertNotSame;
016import static org.junit.Assert.assertSame;
017import static org.junit.Assert.assertTrue;
018import static org.junit.Assert.fail;
019
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.ObjectInputStream;
024import java.io.ObjectOutputStream;
025import java.lang.reflect.Constructor;
026import java.lang.reflect.Type;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Set;
034
035import junit.framework.Assert;
036import junit.framework.AssertionFailedError;
037
038import org.junit.Test;
039import org.picocontainer.ComponentAdapter;
040import org.picocontainer.ComponentFactory;
041import org.picocontainer.DefaultPicoContainer;
042import org.picocontainer.LifecycleStrategy;
043import org.picocontainer.MutablePicoContainer;
044import org.picocontainer.ObjectReference;
045import org.picocontainer.Parameter;
046import org.picocontainer.PicoCompositionException;
047import org.picocontainer.PicoContainer;
048import org.picocontainer.behaviors.AbstractBehavior;
049import org.picocontainer.injectors.AbstractInjector;
050import org.picocontainer.injectors.AdaptingInjection;
051import org.picocontainer.injectors.ConstructorInjection;
052import org.picocontainer.parameters.ConstantParameter;
053import org.picocontainer.references.SimpleReference;
054import org.picocontainer.visitors.AbstractPicoVisitor;
055
056import com.thoughtworks.xstream.XStream;
057import com.thoughtworks.xstream.converters.reflection.PureJavaReflectionProvider;
058import com.thoughtworks.xstream.io.xml.XppDriver;
059
060/**
061 * Test suite for a ComponentAdapter implementation.
062 * 
063 * @author Jörg Schaible
064 */
065@SuppressWarnings("serial")
066public abstract class AbstractComponentAdapterTest  {
067
068    public static final int SERIALIZABLE = 1;
069    public static final int VERIFYING = 2;
070    public static final int INSTANTIATING = 4;
071    public static final int RESOLVING = 8;
072
073    protected abstract Class getComponentAdapterType();
074
075    protected int getComponentAdapterNature() {
076        return SERIALIZABLE | VERIFYING | INSTANTIATING | RESOLVING;
077    }
078
079    protected ComponentFactory createDefaultComponentFactory() {
080        return new AdaptingInjection();
081    }
082
083    // ============================================
084    // Default
085    // ============================================
086
087    /**
088     * Prepare the test <em>verifyWithoutDependencyWorks</em>.
089     * 
090     * @param picoContainer container, may probably not be used.
091     * @return a ComponentAdapter of the type to test for a component without dependencies. Registration in the pico is
092     *         not necessary.
093     */
094    protected abstract ComponentAdapter prepDEF_verifyWithoutDependencyWorks(MutablePicoContainer picoContainer);
095
096    final @Test public void testDEF_verifyWithoutDependencyWorks() {
097        final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
098        final ComponentAdapter componentAdapter = prepDEF_verifyWithoutDependencyWorks(picoContainer);
099        assertSame(getComponentAdapterType(), componentAdapter.getClass());
100        componentAdapter.verify(picoContainer);
101    }
102
103    /**
104     * Prepare the test <em>verifyDoesNotInstantiate</em>.
105     * 
106     * @param picoContainer container, may probably not be used.
107     * @return a ComponentAdapter of the type to test for a component that may throw on instantiation. Registration in
108     *         the pico is not necessary.
109     */
110    protected abstract ComponentAdapter prepDEF_verifyDoesNotInstantiate(MutablePicoContainer picoContainer);
111
112    final @Test public void testDEF_verifyDoesNotInstantiate() {
113        final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
114        final ComponentAdapter componentAdapter = prepDEF_verifyDoesNotInstantiate(picoContainer);
115        assertSame(getComponentAdapterType(), componentAdapter.getClass());
116        final ComponentAdapter notInstantiatablecomponentAdapter = new NotInstantiatableBehavior(
117                componentAdapter);
118        final PicoContainer wrappedPicoContainer = wrapComponentInstances(
119                NotInstantiatableBehavior.class, picoContainer, null);
120        notInstantiatablecomponentAdapter.verify(wrappedPicoContainer);
121    }
122
123    /**
124     * Prepare the test <em>visitable</em>.
125     * 
126     * @return a ComponentAdapter of the type to test. If the ComponentAdapter supports {@link Parameter}, you have to
127     *         select a component, that have some.
128     */
129    protected abstract ComponentAdapter prepDEF_visitable();
130
131    final @Test public void testDEF_visitable() {
132        final ComponentAdapter componentAdapter = prepDEF_visitable();
133        final Class type = getComponentAdapterType();
134        assertSame(type, componentAdapter.getClass());
135        boolean hasParameters = supportsParameters(type);
136        final RecordingVisitor visitor = new RecordingVisitor();
137        visitor.traverse(componentAdapter);
138        final List visitedElements = new ArrayList(visitor.getVisitedElements());
139        assertSame(componentAdapter, visitedElements.get(0));
140        if (hasParameters) {
141            hasParameters = false;
142            for (final Iterator iter = visitedElements.iterator(); iter.hasNext() && !hasParameters;) {
143                hasParameters = Parameter.class.isAssignableFrom(iter.next().getClass());
144            }
145            assertTrue("ComponentAdapter " + type + " supports parameters, provide some", hasParameters);
146        }
147    }
148
149    /**
150     * Prepare the test <em>isAbleToTakeParameters</em>. Overload this function, if the ComponentAdapter to test
151     * supports {@link Parameter}.
152     * 
153     * @param picoContainer container, may probably not be used.
154     * @return a ComponentAdapter of the type to test. Select a component, that has some parameters. Registration in the
155     *         pico is not necessary.
156     */
157    protected ComponentAdapter prepDEF_isAbleToTakeParameters(MutablePicoContainer picoContainer) {
158        final Class type = getComponentAdapterType();
159        boolean hasParameters = supportsParameters(type);
160        if (hasParameters) {
161            fail("You have to overwrite this method for a useful test");
162        }
163        return null;
164    }
165
166    final @Test public void testDEF_isAbleToTakeParameters() {
167        final Class type = getComponentAdapterType();
168        boolean hasParameters = supportsParameters(type);
169        if (hasParameters) {
170            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
171            final ComponentAdapter componentAdapter = prepDEF_isAbleToTakeParameters(picoContainer);
172            assertSame(getComponentAdapterType(), componentAdapter.getClass());
173            final RecordingVisitor visitor = new RecordingVisitor();
174            visitor.traverse(componentAdapter);
175            final List visitedElements = visitor.getVisitedElements();
176            if (hasParameters) {
177                hasParameters = false;
178                for (final Iterator iter = visitedElements.iterator(); iter.hasNext() && !hasParameters;) {
179                    hasParameters = Parameter.class.isAssignableFrom(iter.next().getClass());
180                }
181                assertTrue("ComponentAdapter " + type + " supports parameters, provide some", hasParameters);
182            }
183            final Object instance = componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
184            assertNotNull(instance);
185        }
186    }
187
188    // ============================================
189    // Serializable
190    // ============================================
191
192    /**
193     * Prepare the test <em>isSerializable</em>. Overload this function, if the ComponentAdapter supports
194     * serialization.
195     * 
196     * @param picoContainer container, may probably not be used.
197     * @return a ComponentAdapter of the type to test. Registration in the pico is not necessary.
198     */
199    protected ComponentAdapter prepSER_isSerializable(MutablePicoContainer picoContainer) {
200        throw new AssertionFailedError("You have to overwrite this method for a useful test");
201    }
202
203    final @Test public void testSER_isSerializable() throws IOException, ClassNotFoundException {
204        if ((getComponentAdapterNature() & SERIALIZABLE) > 0) {
205            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
206            final ComponentAdapter componentAdapter = prepSER_isSerializable(picoContainer);
207            assertSame(getComponentAdapterType(), componentAdapter.getClass());
208            final Object instance = componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
209            assertNotNull(instance);
210            final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
211            final ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
212            outputStream.writeObject(componentAdapter);
213            outputStream.close();
214            final ObjectInputStream inputStream = new ObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream
215                    .toByteArray()));
216            final ComponentAdapter serializedComponentAdapter = (ComponentAdapter)inputStream.readObject();
217            inputStream.close();
218            assertEquals(componentAdapter.getComponentKey(), serializedComponentAdapter.getComponentKey());
219            final Object instanceAfterSerialization = serializedComponentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
220            assertNotNull(instanceAfterSerialization);
221            assertSame(instance.getClass(), instanceAfterSerialization.getClass());
222        }
223    }
224
225    /**
226     * Prepare the test <em>isXStreamSerializable</em>. Overload this function, if the ComponentAdapter supports
227     * serialization.
228     * 
229     * @param picoContainer container, may probably not be used.
230     * @return a ComponentAdapter of the type to test. Registration in the pico is not necessary.
231     */
232    protected ComponentAdapter prepSER_isXStreamSerializable(MutablePicoContainer picoContainer) {
233        throw new AssertionFailedError("You have to overwrite this method for a useful test");
234    }
235
236    final @Test public void testSER_isXStreamSerializableWithPureReflection() {
237        if ((getComponentAdapterNature() & SERIALIZABLE) > 0) {
238            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
239            final ComponentAdapter componentAdapter = prepSER_isXStreamSerializable(picoContainer);
240            assertSame(getComponentAdapterType(), componentAdapter.getClass());
241            final Object instance = componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
242            assertNotNull(instance);
243            final XStream xstream = new XStream(new PureJavaReflectionProvider(), new XppDriver());
244            final String xml = xstream.toXML(componentAdapter);
245            final ComponentAdapter serializedComponentAdapter = (ComponentAdapter)xstream.fromXML(xml);
246            assertEquals(componentAdapter.getComponentKey(), serializedComponentAdapter.getComponentKey());
247            final Object instanceAfterSerialization = serializedComponentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
248            assertNotNull(instanceAfterSerialization);
249            assertSame(instance.getClass(), instanceAfterSerialization.getClass());
250        }
251    }
252
253    final @Test public void testSER_isXStreamSerializable() {
254        if ((getComponentAdapterNature() & SERIALIZABLE) > 0) {
255            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
256            final ComponentAdapter componentAdapter = prepSER_isXStreamSerializable(picoContainer);
257            assertSame(getComponentAdapterType(), componentAdapter.getClass());
258            final Object instance = componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
259            assertNotNull(instance);
260            final XStream xstream = new XStream(new XppDriver());
261            final String xml = xstream.toXML(componentAdapter);
262            final ComponentAdapter serializedComponentAdapter = (ComponentAdapter)xstream.fromXML(xml);
263            assertEquals(componentAdapter.getComponentKey(), serializedComponentAdapter.getComponentKey());
264            final Object instanceAfterSerialization = serializedComponentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
265            assertNotNull(instanceAfterSerialization);
266            assertSame(instance.getClass(), instanceAfterSerialization.getClass());
267        }
268    }
269
270    // ============================================
271    // Verifying
272    // ============================================
273
274    /**
275     * Prepare the test <em>verificationFailsWithUnsatisfiedDependency</em>. Overload this function, if the
276     * ComponentAdapter's verification can fail e.g. due to an unresolved dependency.
277     * 
278     * @param picoContainer container, may probably not be used.
279     * @return a ComponentAdapter of the type to test, that fails for the verification, e.g. because of a compoennt with
280     *         missing dependencies. Registration in the pico is not necessary.
281     */
282    protected ComponentAdapter prepVER_verificationFails(MutablePicoContainer picoContainer) {
283        throw new AssertionFailedError("You have to overwrite this method for a useful test");
284    }
285
286    final @Test public void testVER_verificationFails() {
287        if ((getComponentAdapterNature() & VERIFYING) > 0) {
288            final MutablePicoContainer picoContainer = new DefaultPicoContainer();
289            final ComponentAdapter componentAdapter = prepVER_verificationFails(picoContainer);
290            assertSame(getComponentAdapterType(), componentAdapter.getClass());
291            try {
292                componentAdapter.verify(picoContainer);
293                fail("PicoCompositionException expected");
294            } catch (PicoCompositionException e) {
295            } catch (Exception e) {
296                fail("PicoCompositionException expected, but got " + e.getClass().getName());
297            }
298            try {
299                componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
300                fail("PicoCompositionException or PicoCompositionException expected");
301            } catch (PicoCompositionException e) {
302            } catch (Exception e) {
303                fail("PicoCompositionException or PicoCompositionException expected, but got "
304                        + e.getClass().getName());
305            }
306        }
307    }
308
309    // ============================================
310    // Instantiating
311    // ============================================
312
313    /**
314     * Prepare the test <em>createsNewInstances</em>. Overload this function, if the ComponentAdapter is
315     * instantiating. It should create a new instance with every call.
316     * 
317     * @param picoContainer container, may probably not be used.
318     * @return a ComponentAdapter of the type to test. Registration in the pico is not necessary.
319     */
320    protected ComponentAdapter prepINS_createsNewInstances(MutablePicoContainer picoContainer) {
321        throw new AssertionFailedError("You have to overwrite this method for a useful test");
322    }
323
324    final @Test public void testINS_createsNewInstances() {
325        if ((getComponentAdapterNature() & INSTANTIATING) > 0) {
326            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
327            final ComponentAdapter componentAdapter = prepINS_createsNewInstances(picoContainer);
328            assertSame(getComponentAdapterType(), componentAdapter.getClass());
329            final Object instance = componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
330            assertNotNull(instance);
331            assertNotSame(instance, componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class));
332            assertSame(instance.getClass(), componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class).getClass());
333        }
334    }
335
336    /**
337     * Prepare the test <em>errorIsRethrown</em>. Overload this function, if the ComponentAdapter is instantiating.
338     * 
339     * @param picoContainer container, may probably not be used.
340     * @return a ComponentAdapter of the type to test with a component that fails with an {@link Error} at
341     *         instantiation. Registration in the pico is not necessary.
342     */
343    protected ComponentAdapter prepINS_errorIsRethrown(MutablePicoContainer picoContainer) {
344        throw new AssertionFailedError("You have to overwrite this method for a useful test");
345    }
346
347    final @Test public void testINS_errorIsRethrown() {
348        if ((getComponentAdapterNature() & INSTANTIATING) > 0) {
349            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
350            final ComponentAdapter componentAdapter = prepINS_errorIsRethrown(picoContainer);
351            assertSame(getComponentAdapterType(), componentAdapter.getClass());
352            try {
353                componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
354                fail("Thrown Error excpected");
355            } catch (final Error e) {
356                assertEquals("test", e.getMessage());
357            }
358        }
359    }
360
361    /**
362     * Prepare the test <em>runtimeExceptionIsRethrown</em>. Overload this function, if the ComponentAdapter is
363     * instantiating.
364     * 
365     * @param picoContainer container, may probably not be used.
366     * @return a ComponentAdapter of the type to test with a component that fails with a {@link RuntimeException} at
367     *         instantiation. Registration in the pico is not necessary.
368     */
369    protected ComponentAdapter prepINS_runtimeExceptionIsRethrown(MutablePicoContainer picoContainer) {
370        throw new AssertionFailedError("You have to overwrite this method for a useful test");
371    }
372
373    final @Test public void testINS_runtimeExceptionIsRethrown() {
374        if ((getComponentAdapterNature() & INSTANTIATING) > 0) {
375            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
376            final ComponentAdapter componentAdapter = prepINS_runtimeExceptionIsRethrown(picoContainer);
377            assertSame(getComponentAdapterType(), componentAdapter.getClass());
378            try {
379                componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
380                fail("Thrown RuntimeException excpected");
381            } catch (final RuntimeException e) {
382                assertEquals("test", e.getMessage());
383            }
384        }
385    }
386
387    /**
388     * Prepare the test <em>normalExceptionIsRethrownInsidePicoInvocationTargetInitializationException</em>. Overload
389     * this function, if the ComponentAdapter is instantiating.
390     * 
391     * @param picoContainer container, may probably not be used.
392     * @return a ComponentAdapter of the type to test with a component that fails with a
393     *         {@link PicoCompositionException} at instantiation. Registration in the pico is not
394     *         necessary.
395     */
396    protected ComponentAdapter prepINS_normalExceptionIsRethrownInsidePicoInitializationException(
397            MutablePicoContainer picoContainer) {
398        throw new AssertionFailedError("You have to overwrite this method for a useful test");
399    }
400
401    final @Test public void testINS_normalExceptionIsRethrownInsidePicoInitializationException() {
402        if ((getComponentAdapterNature() & INSTANTIATING) > 0) {
403            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
404            final ComponentAdapter componentAdapter = prepINS_normalExceptionIsRethrownInsidePicoInitializationException(picoContainer);
405            assertSame(getComponentAdapterType(), componentAdapter.getClass());
406            try {
407                componentAdapter.getComponentInstance(picoContainer, ComponentAdapter.NOTHING.class);
408                fail("Thrown PicoCompositionException excpected");
409            } catch (final PicoCompositionException e) {
410                assertTrue(e.getCause() instanceof Exception);
411                assertTrue(e.getCause().getMessage().endsWith("test"));
412            }
413        }
414    }
415
416    // ============================================
417    // Resolving
418    // ============================================
419
420    /**
421     * Prepare the test <em>dependenciesAreResolved</em>. Overload this function, if the ComponentAdapter is resolves
422     * dependencies.
423     * 
424     * @param picoContainer container, used to register dependencies.
425     * @return a ComponentAdapter of the type to test with a component that has dependencies. Registration in the pico
426     *         is not necessary.
427     */
428    protected ComponentAdapter prepRES_dependenciesAreResolved(MutablePicoContainer picoContainer) {
429        throw new AssertionFailedError("You have to overwrite this method for a useful test");
430    }
431
432    final @Test public void testRES_dependenciesAreResolved() {
433        if ((getComponentAdapterNature() & RESOLVING) > 0) {
434            final List dependencies = new LinkedList();
435            final Object[] wrapperDependencies = new Object[]{dependencies};
436            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
437            final ComponentAdapter componentAdapter = prepRES_dependenciesAreResolved(picoContainer);
438            assertSame(getComponentAdapterType(), componentAdapter.getClass());
439            assertFalse(picoContainer.getComponentAdapters().contains(componentAdapter));
440            final PicoContainer wrappedPicoContainer = wrapComponentInstances(
441                    CollectingBehavior.class, picoContainer, wrapperDependencies);
442            final Object instance = componentAdapter.getComponentInstance(wrappedPicoContainer, ComponentAdapter.NOTHING.class);
443            assertNotNull(instance);
444            assertTrue(dependencies.size() > 0);
445        }
446    }
447
448    /**
449     * Prepare the test <em>failingVerificationWithCyclicDependencyException</em>. Overload this function, if the
450     * ComponentAdapter is resolves dependencies.
451     * 
452     * @param picoContainer container, used to register dependencies.
453     * @return a ComponentAdapter of the type to test with a component that has cyclic dependencies. You have to
454     *         register the component itself in the pico.
455     */
456    protected ComponentAdapter prepRES_failingVerificationWithCyclicDependencyException(
457            MutablePicoContainer picoContainer) {
458        throw new AssertionFailedError("You have to overwrite this method for a useful test");
459    }
460
461    final @Test public void testRES_failingVerificationWithCyclicDependencyException() {
462        if ((getComponentAdapterNature() & RESOLVING) > 0) {
463            final Set cycleInstances = new HashSet();
464            final ObjectReference cycleCheck = new SimpleReference();
465            final Object[] wrapperDependencies = new Object[]{cycleInstances, cycleCheck};
466            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
467            final ComponentAdapter componentAdapter = prepRES_failingVerificationWithCyclicDependencyException(picoContainer);
468            assertSame(getComponentAdapterType(), componentAdapter.getClass());
469            assertTrue(picoContainer.getComponentAdapters().contains(componentAdapter));
470            final PicoContainer wrappedPicoContainer = wrapComponentInstances(
471                    CycleDetectorBehavior.class, picoContainer, wrapperDependencies);
472            try {
473                componentAdapter.verify(wrappedPicoContainer);
474                fail("Thrown PicoVerificationException excpected");
475            } catch (final AbstractInjector.CyclicDependencyException cycle) {
476                final Class[] dependencies = cycle.getDependencies();
477                assertSame(dependencies[0], dependencies[dependencies.length - 1]);
478            }
479        }
480    }
481
482    /**
483     * Prepare the test <em>failingInstantiationWithCyclicDependencyException</em>. Overload this function, if the
484     * ComponentAdapter is resolves dependencies.
485     * 
486     * @param picoContainer container, used to register dependencies.
487     * @return a ComponentAdapter of the type to test with a component that has cyclic dependencies. You have to
488     *         register the component itself in the pico.
489     */
490    protected ComponentAdapter prepRES_failingInstantiationWithCyclicDependencyException(
491            MutablePicoContainer picoContainer) {
492        throw new AssertionFailedError("You have to overwrite this method for a useful test");
493    }
494
495    final @Test public void testRES_failingInstantiationWithCyclicDependencyException() {
496        if ((getComponentAdapterNature() & RESOLVING) > 0) {
497            final Set cycleInstances = new HashSet();
498            final ObjectReference cycleCheck = new SimpleReference();
499            final Object[] wrapperDependencies = new Object[]{cycleInstances, cycleCheck};
500            final MutablePicoContainer picoContainer = new DefaultPicoContainer(createDefaultComponentFactory());
501            final ComponentAdapter componentAdapter = prepRES_failingInstantiationWithCyclicDependencyException(picoContainer);
502            assertSame(getComponentAdapterType(), componentAdapter.getClass());
503            assertTrue(picoContainer.getComponentAdapters().contains(componentAdapter));
504            final PicoContainer wrappedPicoContainer = wrapComponentInstances(
505                    CycleDetectorBehavior.class, picoContainer, wrapperDependencies);
506            try {
507                componentAdapter.getComponentInstance(wrappedPicoContainer, ComponentAdapter.NOTHING.class);
508                fail("Thrown CyclicDependencyException excpected");
509            } catch (final AbstractInjector.CyclicDependencyException e) {
510                final Class[] dependencies = e.getDependencies();
511                assertSame(dependencies[0], dependencies[dependencies.length - 1]);
512            }
513        }
514    }
515
516    // ============================================
517    // Model & Helpers
518    // ============================================
519
520    static class RecordingVisitor extends AbstractPicoVisitor {
521        private final List visitedElements = new LinkedList();
522
523        public boolean visitContainer(PicoContainer pico) {
524            visitedElements.add(pico);
525            return CONTINUE_TRAVERSAL;
526        }
527
528        public void visitComponentAdapter(ComponentAdapter componentAdapter) {
529            visitedElements.add(componentAdapter);
530        }
531
532        public void visitComponentFactory(ComponentFactory componentFactory) {
533            visitedElements.add(componentFactory);
534        }
535
536        public void visitParameter(Parameter parameter) {
537            visitedElements.add(parameter);
538        }
539
540        List getVisitedElements() {
541            return visitedElements;
542        }
543    }
544
545    static public class NotInstantiatableBehavior extends AbstractBehavior {
546        public NotInstantiatableBehavior(final ComponentAdapter delegate) {
547            super(delegate);
548        }
549
550        public Object getComponentInstance(final PicoContainer container, Type into) {
551            Assert.fail("Not instantiatable");
552            return null;
553        }
554        public String getDescriptor() {
555            return null;
556        }
557        
558    }
559
560    static public class CollectingBehavior extends AbstractBehavior {
561        final List list;
562
563        public CollectingBehavior(final ComponentAdapter delegate, final List list) {
564            super(delegate);
565            this.list = list;
566        }
567
568        public Object getComponentInstance(final PicoContainer container, Type into) {
569            final Object result = super.getComponentInstance(container, into);
570            list.add(result);
571            return result;
572        }
573
574        public String getDescriptor() {
575            return "xxx";
576        }
577    }
578
579    static public class CycleDetectorBehavior extends AbstractBehavior {
580        private final Set set;
581        private final ObjectReference reference;
582
583        public CycleDetectorBehavior(
584                final ComponentAdapter delegate, final Set set, final ObjectReference reference) {
585            super(delegate);
586            this.set = set;
587            this.reference = reference;
588        }
589
590        public Object getComponentInstance(final PicoContainer container, Type into) {
591            if (set.contains(this)) {
592                reference.set(this);
593            } else {
594                set.add(this);
595            }
596            return super.getComponentInstance(container, into);
597        }
598
599        public String getDescriptor() {
600            return "xxx";
601        }
602    }
603
604    public static final class RecordingLifecycleStrategy implements LifecycleStrategy {
605        private final StringBuffer recorder;
606        
607        public RecordingLifecycleStrategy(StringBuffer recorder) {
608            this.recorder = recorder;
609        }
610    
611        public void start(Object component) {
612            recorder.append("<start");
613        }
614    
615        public void stop(Object component) {
616            recorder.append("<stop");
617        }
618    
619        public void dispose(Object component) {
620            recorder.append("<dispose");
621        }
622        
623        public boolean hasLifecycle(Class type) {
624            return true;
625        }
626
627        public boolean isLazy(ComponentAdapter<?> adapter) {
628            return false;
629        }
630
631        public String recording() {
632            return recorder.toString();
633        }
634    }
635
636    final protected PicoContainer wrapComponentInstances(
637            final Class decoratingComponentAdapterClass, final PicoContainer picoContainer,
638            final Object[] wrapperDependencies) {
639        assertTrue(AbstractBehavior.class.isAssignableFrom(decoratingComponentAdapterClass));
640        final MutablePicoContainer mutablePicoContainer = new DefaultPicoContainer();
641        final int size = (wrapperDependencies != null ? wrapperDependencies.length : 0) + 1;
642        final Collection allComponentAdapters = picoContainer.getComponentAdapters();
643        for (Object allComponentAdapter : allComponentAdapters) {
644            final Parameter[] parameters = new Parameter[size];
645            parameters[0] = new ConstantParameter(allComponentAdapter);
646            for (int i = 1; i < parameters.length; i++) {
647                parameters[i] = new ConstantParameter(wrapperDependencies[i - 1]);
648            }
649            final MutablePicoContainer instantiatingPicoContainer = new DefaultPicoContainer(
650                new ConstructorInjection());
651            instantiatingPicoContainer.addComponent(
652                "decorator", decoratingComponentAdapterClass, parameters);
653            mutablePicoContainer.addAdapter((ComponentAdapter)instantiatingPicoContainer
654                .getComponent("decorator"));
655        }
656        return mutablePicoContainer;
657    }
658
659    private boolean supportsParameters(final Class type) {
660        boolean hasParameters = false;
661        final Constructor[] constructors = type.getConstructors();
662        for (int i = 0; i < constructors.length && !hasParameters; i++) {
663            final Constructor constructor = constructors[i];
664            final Class[] parameterTypes = constructor.getParameterTypes();
665            for (final Class parameterType : parameterTypes) {
666                if (Parameter.class.isAssignableFrom(parameterType)
667                    || (parameterType.isArray() && Parameter.class.isAssignableFrom(parameterType
668                    .getComponentType()))) {
669                    hasParameters = true;
670                    break;
671                }
672            }
673        }
674        return hasParameters;
675    }
676}