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.injectors;
011
012 import static org.junit.Assert.assertEquals;
013 import static org.junit.Assert.assertNotNull;
014 import static org.junit.Assert.assertTrue;
015 import static org.junit.Assert.fail;
016 import static org.picocontainer.tck.MockFactory.mockeryWithCountingNamingScheme;
017
018 import java.awt.event.ActionEvent;
019 import java.awt.event.ActionListener;
020 import java.lang.reflect.Constructor;
021 import java.lang.reflect.InvocationTargetException;
022 import java.util.HashMap;
023 import java.util.Map;
024
025 import javax.swing.AbstractButton;
026
027 import org.hamcrest.BaseMatcher;
028 import org.hamcrest.Description;
029 import org.hamcrest.Matcher;
030 import org.jmock.Expectations;
031 import org.jmock.Mockery;
032 import org.junit.Test;
033 import org.picocontainer.ComponentAdapter;
034 import org.picocontainer.ComponentMonitor;
035 import org.picocontainer.DefaultPicoContainer;
036 import org.picocontainer.MutablePicoContainer;
037 import org.picocontainer.Parameter;
038 import org.picocontainer.PicoCompositionException;
039 import org.picocontainer.PicoContainer;
040 import org.picocontainer.InjectionFactory;
041 import org.picocontainer.lifecycle.NullLifecycleStrategy;
042 import org.picocontainer.monitors.AbstractComponentMonitor;
043 import org.picocontainer.monitors.NullComponentMonitor;
044 import org.picocontainer.parameters.ComponentParameter;
045 import org.picocontainer.parameters.ConstantParameter;
046 import org.picocontainer.tck.AbstractComponentAdapterTest;
047 import org.picocontainer.testmodel.DependsOnTouchable;
048 import org.picocontainer.testmodel.NullLifecycle;
049 import org.picocontainer.testmodel.SimpleTouchable;
050 import org.picocontainer.testmodel.Touchable;
051
052
053 @SuppressWarnings("serial")
054 public class ConstructorInjectorTestCase extends AbstractComponentAdapterTest {
055
056 private Mockery mockery = mockeryWithCountingNamingScheme();
057
058 protected Class getComponentAdapterType() {
059 return ConstructorInjector.class;
060 }
061
062 protected ComponentAdapter prepDEF_verifyWithoutDependencyWorks(MutablePicoContainer picoContainer) {
063 return new ConstructorInjector("foo", A.class, null, new NullComponentMonitor(), false);
064 }
065
066 public static class A {
067 public A() {
068 fail("verification should not instantiate");
069 }
070 }
071
072 public static class B {
073 public B(A a) {
074 fail("verification should not instantiate");
075 }
076 }
077
078 protected ComponentAdapter prepDEF_verifyDoesNotInstantiate(MutablePicoContainer picoContainer) {
079 picoContainer.addComponent(A.class);
080 return new ConstructorInjector(B.class, B.class, null, new NullComponentMonitor(), false);
081 }
082
083 protected ComponentAdapter prepDEF_visitable() {
084 return new ConstructorInjector("bar", B.class, new Parameter[] {ComponentParameter.DEFAULT} , new NullComponentMonitor(), false);
085 }
086
087 protected ComponentAdapter prepDEF_isAbleToTakeParameters(MutablePicoContainer picoContainer) {
088 picoContainer.addComponent(SimpleTouchable.class);
089 return new ConstructorInjector(
090 NamedDependsOnTouchable.class, NamedDependsOnTouchable.class,
091 new Parameter[] {ComponentParameter.DEFAULT, new ConstantParameter("Name")} , new NullComponentMonitor(), false);
092 }
093
094 protected ComponentAdapter prepSER_isSerializable(MutablePicoContainer picoContainer) {
095 return new ConstructorInjector(SimpleTouchable.class, SimpleTouchable.class, null, new NullComponentMonitor(), false);
096 }
097
098 protected ComponentAdapter prepSER_isXStreamSerializable(final MutablePicoContainer picoContainer) {
099 return prepSER_isSerializable(picoContainer);
100 }
101
102 public static class NamedDependsOnTouchable extends DependsOnTouchable {
103 public NamedDependsOnTouchable(Touchable t, String name) {
104 super(t);
105 }
106 }
107
108 protected ComponentAdapter prepVER_verificationFails(MutablePicoContainer picoContainer) {
109 return new ConstructorInjector(DependsOnTouchable.class, DependsOnTouchable.class, null, new NullComponentMonitor(), false);
110 }
111
112 protected ComponentAdapter prepINS_createsNewInstances(MutablePicoContainer picoContainer) {
113 return new ConstructorInjector(SimpleTouchable.class, SimpleTouchable.class, null, new NullComponentMonitor(), false);
114 }
115
116 public static class Erroneous {
117 public Erroneous() {
118 throw new VerifyError("test");
119 }
120 }
121
122 protected ComponentAdapter prepINS_errorIsRethrown(MutablePicoContainer picoContainer) {
123 return new ConstructorInjector(Erroneous.class, Erroneous.class, null, new NullComponentMonitor(), false);
124 }
125
126 public static class RuntimeThrowing {
127 public RuntimeThrowing() {
128 throw new RuntimeException("test");
129 }
130 }
131
132 protected ComponentAdapter prepINS_runtimeExceptionIsRethrown(MutablePicoContainer picoContainer) {
133 return new ConstructorInjector(RuntimeThrowing.class, RuntimeThrowing.class, null, new NullComponentMonitor(), false);
134 }
135
136 public static class NormalExceptionThrowing {
137 public NormalExceptionThrowing() throws Exception {
138 throw new Exception("test");
139 }
140 }
141
142 protected ComponentAdapter prepINS_normalExceptionIsRethrownInsidePicoInitializationException(
143 MutablePicoContainer picoContainer) {
144 return new ConstructorInjector(NormalExceptionThrowing.class, NormalExceptionThrowing.class, null, new NullComponentMonitor(), false);
145 }
146
147 protected ComponentAdapter prepRES_dependenciesAreResolved(MutablePicoContainer picoContainer) {
148 picoContainer.addComponent(SimpleTouchable.class);
149 return new ConstructorInjector(DependsOnTouchable.class, DependsOnTouchable.class, null, new NullComponentMonitor(), false);
150 }
151
152 public static class C1 {
153 public C1(C2 c2) {
154 fail("verification should not instantiate");
155 }
156 }
157
158 public static class C2 {
159 public C2(C1 c1) {
160 fail("verification should not instantiate");
161 }
162 }
163
164 protected ComponentAdapter prepRES_failingVerificationWithCyclicDependencyException(MutablePicoContainer picoContainer) {
165 final ComponentAdapter componentAdapter = new ConstructorInjector(C1.class, C1.class, null, new NullComponentMonitor(), false);
166 picoContainer.addAdapter(componentAdapter);
167 picoContainer.addComponent(C2.class, C2.class);
168 return componentAdapter;
169 }
170
171 protected ComponentAdapter prepRES_failingInstantiationWithCyclicDependencyException(MutablePicoContainer picoContainer) {
172 final ComponentAdapter componentAdapter = new ConstructorInjector(C1.class, C1.class, null, new NullComponentMonitor(), false);
173 picoContainer.addAdapter(componentAdapter);
174 picoContainer.addComponent(C2.class, C2.class);
175 return componentAdapter;
176 }
177
178 @Test public void testNormalExceptionThrownInCtorIsRethrownInsideInvocationTargetExeption() {
179 DefaultPicoContainer picoContainer = new DefaultPicoContainer();
180 picoContainer.addComponent(NormalExceptionThrowing.class);
181 try {
182 picoContainer.getComponent(NormalExceptionThrowing.class);
183 fail();
184 } catch (PicoCompositionException e) {
185 assertEquals("test", e.getCause().getMessage());
186 }
187 }
188
189 public static class InstantiationExceptionThrowing {
190 public InstantiationExceptionThrowing() {
191 throw new RuntimeException("Barf");
192 }
193 }
194
195 @Test public void testInstantiationExceptionThrownInCtorIsRethrownInsideInvocationTargetExeption() {
196 DefaultPicoContainer picoContainer = new DefaultPicoContainer();
197 try {
198 picoContainer.addComponent(InstantiationExceptionThrowing.class);
199 picoContainer.getComponent(InstantiationExceptionThrowing.class);
200 fail();
201 } catch (RuntimeException e) {
202 assertEquals("Barf", e.getMessage());
203 }
204 }
205
206 public static class AllConstructorsArePrivate {
207 private AllConstructorsArePrivate() {
208 }
209 }
210
211 @Test public void testPicoInitializationExceptionThrownBecauseOfFilteredConstructors() {
212 DefaultPicoContainer picoContainer = new DefaultPicoContainer();
213 try {
214 picoContainer.addComponent(AllConstructorsArePrivate.class);
215 picoContainer.getComponent(AllConstructorsArePrivate.class);
216 fail();
217 } catch (PicoCompositionException e) {
218 String s = e.getMessage();
219 assertTrue(s.indexOf("constructors were not accessible") > 0);
220 assertTrue(s.indexOf(AllConstructorsArePrivate.class.getName()) > 0);
221 }
222 }
223
224 @Test public void testRegisterInterfaceShouldFail() throws PicoCompositionException {
225 MutablePicoContainer pico = new DefaultPicoContainer();
226
227 try {
228 pico.addComponent(Runnable.class);
229 fail("Shouldn't be allowed to register abstract classes or interfaces.");
230 } catch (AbstractInjector.NotConcreteRegistrationException e) {
231 assertEquals(Runnable.class, e.getComponentImplementation());
232 assertTrue(e.getMessage().indexOf(Runnable.class.getName()) > 0);
233 }
234 }
235
236 @Test public void testRegisterAbstractShouldFail() throws PicoCompositionException {
237 MutablePicoContainer pico = new DefaultPicoContainer();
238
239 try {
240 pico.addComponent(AbstractButton.class);
241 fail("Shouldn't be allowed to register abstract classes or interfaces.");
242 } catch (AbstractInjector.NotConcreteRegistrationException e) {
243 assertEquals(AbstractButton.class, e.getComponentImplementation());
244 assertTrue(e.getMessage().indexOf(AbstractButton.class.getName()) > 0);
245 }
246 }
247
248 private static class Private {
249 private Private() {
250 }
251 }
252
253 private static class NotYourBusiness {
254 private NotYourBusiness(Private aPrivate) {
255 assertNotNull(aPrivate);
256 }
257 }
258
259 static public class Component201 {
260 public Component201(final String s) {
261 }
262
263 protected Component201(final Integer i, final Boolean b) {
264 fail("Wrong constructor taken.");
265 }
266 }
267
268 // http://jira.codehaus.org/browse/PICO-201
269 @Test public void testShouldNotConsiderNonPublicConstructors() {
270 DefaultPicoContainer pico = new DefaultPicoContainer();
271 pico.addComponent(Component201.class);
272 pico.addComponent(new Integer(2));
273 pico.addComponent(Boolean.TRUE);
274 pico.addComponent("Hello");
275 assertNotNull(pico.getComponent(Component201.class));
276 }
277
278 @Test public void testMonitoringHappensBeforeAndAfterInstantiation() throws NoSuchMethodException {
279 final ComponentMonitor monitor = mockery.mock(ComponentMonitor.class);
280 final Constructor emptyHashMapCtor = HashMap.class.getConstructor();
281 final Matcher<Long> durationIsGreaterThanOrEqualToZero = new BaseMatcher<Long>() {
282 public boolean matches(Object item) {
283 Long duration = (Long)item;
284 return 0 <= duration;
285 }
286
287 public void describeTo(Description description) {
288 description.appendText("The endTime wasn't after the startTime");
289 }
290 };
291
292 final Matcher<Object> isAHashMapThatWozCreated = new BaseMatcher<Object>() {
293 public boolean matches(Object item) {
294 return item instanceof HashMap;
295 }
296
297 public void describeTo(Description description) {
298 description.appendText("Should have been a hashmap");
299 }
300 };
301
302 final Matcher<Object[]> injectedIsEmptyArray = new BaseMatcher<Object[]>() {
303 public boolean matches(Object item) {
304 Object[] injected = (Object[])item;
305 return 0 == injected.length;
306 }
307 public void describeTo(Description description) {
308 description.appendText("Should have had nothing injected into it");
309 }
310 };
311
312 mockery.checking(new Expectations(){{
313 one(monitor).instantiating(with(any(PicoContainer.class)), (ComponentAdapter)with(a(ConstructorInjector.class)), with(equal(emptyHashMapCtor)));
314 will(returnValue(emptyHashMapCtor));
315 one(monitor).instantiated(with(any(PicoContainer.class)), (ComponentAdapter)with(a(ConstructorInjector.class)), with(equal(emptyHashMapCtor)),
316 with(isAHashMapThatWozCreated), with(injectedIsEmptyArray),
317 with(durationIsGreaterThanOrEqualToZero));
318 }});
319
320 ConstructorInjector cica = new ConstructorInjector(
321 Map.class, HashMap.class, new Parameter[0], monitor, false);
322 cica.getComponentInstance(null, ComponentAdapter.NOTHING.class);
323 }
324
325 @Test public void testMonitoringHappensBeforeAndOnFailOfImpossibleComponentsInstantiation() throws NoSuchMethodException {
326 final ComponentMonitor monitor = mockery.mock(ComponentMonitor.class);
327 final Constructor barfingActionListenerCtor = BarfingActionListener.class.getConstructor();
328
329 final Matcher<Exception> isITE = new BaseMatcher<Exception>() {
330 public boolean matches(Object item) {
331 Exception ex = (Exception)item;
332 return ex instanceof InvocationTargetException;
333 }
334
335 public void describeTo(Description description) {
336 description.appendText("Should have been unable to instantiate");
337 }
338 };
339
340 mockery.checking(new Expectations(){{
341 one(monitor).instantiating(with(any(PicoContainer.class)), (ComponentAdapter)with(a(ConstructorInjector.class)), with(equal(barfingActionListenerCtor)));
342 will(returnValue(barfingActionListenerCtor));
343 one(monitor).instantiationFailed(with(any(PicoContainer.class)), (ComponentAdapter)with(a(ConstructorInjector.class)), with(equal(barfingActionListenerCtor)),
344 with(isITE));
345 }});
346
347
348 ConstructorInjector cica = new ConstructorInjector(
349 ActionListener.class, BarfingActionListener.class, new Parameter[0], monitor, false);
350 try {
351 cica.getComponentInstance(null, ComponentAdapter.NOTHING.class);
352 fail("Should barf");
353 } catch (RuntimeException e) {
354 assertEquals("Barf!", e.getMessage());
355 }
356 }
357
358 private static class BarfingActionListener implements ActionListener {
359 public BarfingActionListener() {
360 throw new RuntimeException("Barf!");
361 }
362
363 public void actionPerformed(ActionEvent e) {
364 }
365 }
366
367 public static class One {
368 public One(Two two) {
369 two.inc();
370 }
371 }
372 public static class Two {
373 private int inc;
374 public void inc() {
375 inc++;
376 }
377
378 public long howMany() {
379 return inc;
380 }
381 }
382
383 /*
384 * (TODO: On some machines, the number of iterations aren't enough.)
385 */
386 @Test public void testSpeedOfRememberedConstructor() {
387 long with, without;
388 injectionFactory = new ForgetfulConstructorInjection();
389 timeIt(); // discard
390 timeIt(); // discard
391 timeIt(); // discard
392 without = timeIt();
393 injectionFactory = new ConstructorInjection();
394 with = timeIt();
395 assertTrue("'with' should be less than 'without' but they were in fact: " + with + ", and " + without, with < without);
396 }
397
398 InjectionFactory injectionFactory;
399 private long timeIt() {
400 int iterations = 20000;
401 DefaultPicoContainer dpc = new DefaultPicoContainer(injectionFactory);
402 Two two = new Two();
403 dpc.addComponent(two);
404 dpc.addComponent(One.class);
405 long start = System.currentTimeMillis();
406 for (int x = 0; x < iterations; x++) {
407 dpc.getComponent(One.class);
408 }
409 long end = System.currentTimeMillis();
410 assertEquals(iterations, two.howMany());
411 return end-start;
412 }
413
414 }