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