1
1
package io .cucumber .core .runtime ;
2
2
3
+ import io .cucumber .core .backend .DefaultObjectFactory ;
3
4
import io .cucumber .core .backend .ObjectFactory ;
4
5
import io .cucumber .core .backend .Options ;
5
6
import io .cucumber .core .exception .CucumberException ;
6
7
7
- import java .lang .reflect .Constructor ;
8
- import java .util .HashMap ;
9
8
import java .util .Iterator ;
10
- import java .util .Map ;
11
9
import java .util .ServiceLoader ;
12
10
import java .util .function .Supplier ;
13
11
import java .util .stream .Collectors ;
14
12
import java .util .stream .Stream ;
15
13
16
14
import static java .util .Objects .requireNonNull ;
17
15
16
+ /**
17
+ * Loads an instance of {@link ObjectFactory} using the {@link ServiceLoader}
18
+ * mechanism.
19
+ * <p>
20
+ * Will load an instance of the class provided by
21
+ * {@link Options#getObjectFactoryClass()}. If
22
+ * {@link Options#getObjectFactoryClass()} does not provide a class and there is
23
+ * exactly one {@code ObjectFactory} instance available that instance will be
24
+ * used.
25
+ * <p>
26
+ * Otherwise {@link DefaultObjectFactory} with no dependency injection
27
+ */
18
28
public final class ObjectFactoryServiceLoader {
19
29
20
30
private final Supplier <ClassLoader > classLoaderSupplier ;
@@ -25,28 +35,12 @@ public ObjectFactoryServiceLoader(Supplier<ClassLoader> classLoaderSupplier, Opt
25
35
this .options = requireNonNull (options );
26
36
}
27
37
28
- /**
29
- * Loads an instance of {@link ObjectFactory} using the
30
- * {@link ServiceLoader} mechanism.
31
- * <p>
32
- * Will load an instance of the class provided by
33
- * {@link Options#getObjectFactoryClass()}. If
34
- * {@link Options#getObjectFactoryClass()} does not provide a class and
35
- * there is exactly one {@code ObjectFactory} instance available that
36
- * instance will be used.
37
- * <p>
38
- * Otherwise {@link DefaultJavaObjectFactory} with no dependency injection
39
- * capabilities will be used.
40
- *
41
- * @return an instance of {@link ObjectFactory}
42
- */
43
38
ObjectFactory loadObjectFactory () {
44
39
Class <? extends ObjectFactory > objectFactoryClass = options .getObjectFactoryClass ();
45
40
ClassLoader classLoader = classLoaderSupplier .get ();
46
41
ServiceLoader <ObjectFactory > loader = ServiceLoader .load (ObjectFactory .class , classLoader );
47
42
if (objectFactoryClass == null ) {
48
43
return loadSingleObjectFactoryOrDefault (loader );
49
-
50
44
}
51
45
52
46
return loadSelectedObjectFactory (loader , objectFactoryClass );
@@ -55,17 +49,34 @@ ObjectFactory loadObjectFactory() {
55
49
private static ObjectFactory loadSingleObjectFactoryOrDefault (ServiceLoader <ObjectFactory > loader ) {
56
50
Iterator <ObjectFactory > objectFactories = loader .iterator ();
57
51
58
- ObjectFactory objectFactory ;
59
- if (objectFactories .hasNext ()) {
52
+ // Find the first non-default object factory,
53
+ // or the default as a side effect.
54
+ ObjectFactory objectFactory = null ;
55
+ while (objectFactories .hasNext ()) {
60
56
objectFactory = objectFactories .next ();
61
- } else {
62
- objectFactory = new DefaultJavaObjectFactory ();
57
+ if (!(objectFactory instanceof DefaultObjectFactory )) {
58
+ break ;
59
+ }
60
+ }
61
+
62
+ if (objectFactory == null ) {
63
+ throw new CucumberException ("" +
64
+ "Could not find any object factory.\n " +
65
+ "\n " +
66
+ "Cucumber uses SPI to discover object factory implementations.\n " +
67
+ "This typically happens when using shaded jars. Make sure\n " +
68
+ "to merge all SPI definitions in META-INF/services correctly" );
63
69
}
64
70
65
- if (objectFactories .hasNext ()) {
71
+ // Check if there are no other non-default object factories
72
+ while (objectFactories .hasNext ()) {
66
73
ObjectFactory extraObjectFactory = objectFactories .next ();
74
+ if (extraObjectFactory instanceof DefaultObjectFactory ) {
75
+ continue ;
76
+ }
67
77
throw new CucumberException (getMultipleObjectFactoryLogMessage (objectFactory , extraObjectFactory ));
68
78
}
79
+
69
80
return objectFactory ;
70
81
}
71
82
@@ -80,8 +91,10 @@ private static ObjectFactory loadSelectedObjectFactory(
80
91
81
92
throw new CucumberException ("" +
82
93
"Could not find object factory " + objectFactoryClass .getName () + ".\n " +
94
+ "\n " +
83
95
"Cucumber uses SPI to discover object factory implementations.\n " +
84
- "Has the class been registered with SPI and is it available on the classpath?" );
96
+ "Has the class been registered with SPI and is it available on\n " +
97
+ "the classpath?" );
85
98
}
86
99
87
100
private static String getMultipleObjectFactoryLogMessage (ObjectFactory ... objectFactories ) {
@@ -90,60 +103,15 @@ private static String getMultipleObjectFactoryLogMessage(ObjectFactory... object
90
103
.map (Class ::getName )
91
104
.collect (Collectors .joining (", " ));
92
105
93
- return "More than one Cucumber ObjectFactory was found in the classpath\n " +
106
+ return "More than one Cucumber ObjectFactory was found on the classpath\n " +
94
107
"\n " +
95
108
"Found: " + factoryNames + "\n " +
96
109
"\n " +
97
- "You may have included, for instance, cucumber-spring AND cucumber-guice as part of\n " +
98
- "your dependencies. When this happens, Cucumber can't decide which to use.\n " +
99
- "In order to enjoy dependency injection features, either remove the unnecessary dependencies" +
100
- "from your classpath or use the `cucumber.object-factory` property or `@CucumberOptions(objectFactory=...)` to select one.\n " ;
101
- }
102
-
103
- /**
104
- * Creates glue instances. Does not provide Dependency Injection.
105
- * <p>
106
- * All glue classes must have a public no-argument constructor.
107
- */
108
- static class DefaultJavaObjectFactory implements ObjectFactory {
109
-
110
- private final Map <Class <?>, Object > instances = new HashMap <>();
111
-
112
- public void start () {
113
- // No-op
114
- }
115
-
116
- public void stop () {
117
- instances .clear ();
118
- }
119
-
120
- public boolean addClass (Class <?> clazz ) {
121
- return true ;
122
- }
123
-
124
- public <T > T getInstance (Class <T > type ) {
125
- T instance = type .cast (instances .get (type ));
126
- if (instance == null ) {
127
- instance = cacheNewInstance (type );
128
- }
129
- return instance ;
130
- }
131
-
132
- private <T > T cacheNewInstance (Class <T > type ) {
133
- try {
134
- Constructor <T > constructor = type .getConstructor ();
135
- T instance = constructor .newInstance ();
136
- instances .put (type , instance );
137
- return instance ;
138
- } catch (NoSuchMethodException e ) {
139
- throw new CucumberException (String .format (
140
- "%s doesn't have an empty constructor. If you need dependency injection, put cucumber-picocontainer on the classpath" ,
141
- type ), e );
142
- } catch (Exception e ) {
143
- throw new CucumberException (String .format ("Failed to instantiate %s" , type ), e );
144
- }
145
- }
146
-
110
+ "You may have included, for instance, cucumber-spring AND cucumber-guice as part\n " +
111
+ "of your dependencies. When this happens, Cucumber can't decide which to use.\n " +
112
+ "In order to enjoy dependency injection features, either remove the unnecessary\n " +
113
+ "dependencies from your classpath or use the `cucumber.object-factory` property\n " +
114
+ "or `@CucumberOptions(objectFactory=...)` to select one.\n " ;
147
115
}
148
116
149
117
}
0 commit comments