19
19
import java .net .URISyntaxException ;
20
20
import java .net .URL ;
21
21
import java .util .HashMap ;
22
- import java .util .Map ;
23
22
import java .util .regex .Matcher ;
24
23
import java .util .regex .Pattern ;
25
24
30
29
* This class creates plugin instances from a String.
31
30
* <p>
32
31
* The String is of the form name[:output] where name is either a fully qualified class name or one of the built-in
33
- * short names. The output is optional for some plugin (and mandatory for some) and must refer to a path on the
34
- * file system.
32
+ * short names. The output is optional for some plugins (and mandatory for some) and must have a single argument constructor
33
+ * with one of the following types:
34
+ * <p>
35
+ * <ul>
36
+ * <li>{@link String}</li>
37
+ * <li>{@link Appendable}</li>
38
+ * <li>{@link URI}</li>
39
+ * <li>{@link URL}</li>
40
+ * <li>{@link File}</li>
41
+ * </ul>
35
42
*
36
43
* @see Plugin
37
44
*/
38
45
public final class PluginFactory {
39
- private final Class [] CTOR_ARGS = new Class []{null , Appendable .class , URI .class , URL .class , File .class };
46
+ private final Class [] CTOR_PARAMETERS = new Class []{String . class , Appendable .class , URI .class , URL .class , File .class };
40
47
41
48
private static final HashMap <String , Class <? extends Plugin >> PLUGIN_CLASSES = new HashMap <String , Class <? extends Plugin >>() {{
42
49
put ("null" , NullFormatter .class );
@@ -51,7 +58,7 @@ public final class PluginFactory {
51
58
put ("default_summary" , DefaultSummaryPrinter .class );
52
59
put ("null_summary" , NullSummaryPrinter .class );
53
60
}};
54
- private static final Pattern PLUGIN_WITH_FILE_PATTERN = Pattern .compile ("([^:]+):(.*)" );
61
+ private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern .compile ("([^:]+):(.*)" );
55
62
private String defaultOutFormatter = null ;
56
63
57
64
private Appendable defaultOut = new PrintStream (System .out ) {
@@ -62,88 +69,101 @@ public void close() {
62
69
};
63
70
64
71
public Plugin create (String pluginString ) {
65
- Matcher pluginWithFile = PLUGIN_WITH_FILE_PATTERN .matcher (pluginString );
72
+ Matcher pluginWithArgument = PLUGIN_WITH_ARGUMENT_PATTERN .matcher (pluginString );
66
73
String pluginName ;
67
- String path = null ;
68
- if (pluginWithFile .matches ()) {
69
- pluginName = pluginWithFile .group (1 );
70
- path = pluginWithFile .group (2 );
74
+ String argument ;
75
+ if (pluginWithArgument .matches ()) {
76
+ pluginName = pluginWithArgument .group (1 );
77
+ argument = pluginWithArgument .group (2 );
71
78
} else {
72
79
pluginName = pluginString ;
80
+ argument = null ;
73
81
}
74
82
Class <? extends Plugin > pluginClass = pluginClass (pluginName );
75
83
try {
76
- return instantiate (pluginString , pluginClass , path );
84
+ return instantiate (pluginString , pluginClass , argument );
77
85
} catch (IOException e ) {
78
86
throw new CucumberException (e );
79
87
} catch (URISyntaxException e ) {
80
88
throw new CucumberException (e );
81
89
}
82
90
}
83
91
84
- private <T extends Plugin > T instantiate (String pluginString , Class <T > pluginClass , String pathOrUrl ) throws IOException , URISyntaxException {
85
- for (Class ctorArgClass : CTOR_ARGS ) {
86
- Constructor <T > constructor = findConstructor (pluginClass , ctorArgClass );
87
- if (constructor != null ) {
88
- Object ctorArg = convertOrNull (pathOrUrl , ctorArgClass , pluginString );
89
- try {
90
- if (ctorArgClass == null ) {
91
- return constructor .newInstance ();
92
- } else {
93
- if (ctorArg == null ) {
94
- throw new CucumberException (String .format ("You must supply an output argument to %s. Like so: %s:output" , pluginString , pluginString ));
95
- }
96
- return constructor .newInstance (ctorArg );
97
- }
98
- } catch (InstantiationException e ) {
99
- throw new CucumberException (e );
100
- } catch (IllegalAccessException e ) {
101
- throw new CucumberException (e );
102
- } catch (InvocationTargetException e ) {
103
- throw new CucumberException (e .getTargetException ());
104
- }
105
- }
92
+ private <T extends Plugin > T instantiate (String pluginString , Class <T > pluginClass , String argument ) throws IOException , URISyntaxException {
93
+ Constructor <T > single = findSingleArgConstructor (pluginClass );
94
+ Constructor <T > empty = findEmptyConstructor (pluginClass );
95
+
96
+ if (single != null ) {
97
+ Object ctorArg = convertOrNull (argument , single .getParameterTypes ()[0 ], pluginString );
98
+ if (ctorArg != null )
99
+ return newInstance (single , ctorArg );
106
100
}
107
- throw new CucumberException (String .format ("%s must have a constructor that is either empty or a single arg of one of: %s" , pluginClass , asList (CTOR_ARGS )));
101
+ if (argument == null && empty != null ) {
102
+ return newInstance (empty );
103
+ }
104
+ if (single != null )
105
+ throw new CucumberException (String .format ("You must supply an output argument to %s. Like so: %s:output" , pluginString , pluginString ));
106
+
107
+ throw new CucumberException (String .format ("%s must have a constructor that is either empty or a single arg of one of: %s" , pluginClass , asList (CTOR_PARAMETERS )));
108
108
}
109
109
110
- private Object convertOrNull (String pathOrUrl , Class ctorArgClass , String formatterString ) throws IOException , URISyntaxException {
111
- if (ctorArgClass == null ) {
112
- return null ;
110
+ private <T extends Plugin > T newInstance (Constructor <T > constructor , Object ... ctorArgs ) {
111
+ try {
112
+ return constructor .newInstance (ctorArgs );
113
+ } catch (InstantiationException e ) {
114
+ throw new CucumberException (e );
115
+ } catch (IllegalAccessException e ) {
116
+ throw new CucumberException (e );
117
+ } catch (InvocationTargetException e ) {
118
+ throw new CucumberException (e .getTargetException ());
113
119
}
114
- if (ctorArgClass .equals (URI .class )) {
115
- if (pathOrUrl != null ) {
116
- return new URI (pathOrUrl );
120
+ }
121
+
122
+ private Object convertOrNull (String arg , Class ctorArgClass , String formatterString ) throws IOException , URISyntaxException {
123
+ if (arg == null ) {
124
+ if (ctorArgClass .equals (Appendable .class )) {
125
+ return defaultOutOrFailIfAlreadyUsed (formatterString );
126
+ } else {
127
+ return null ;
117
128
}
118
129
}
130
+ if (ctorArgClass .equals (URI .class )) {
131
+ return new URI (arg );
132
+ }
119
133
if (ctorArgClass .equals (URL .class )) {
120
- if (pathOrUrl != null ) {
121
- return toURL (pathOrUrl );
122
- }
134
+ return toURL (arg );
123
135
}
124
136
if (ctorArgClass .equals (File .class )) {
125
- if (pathOrUrl != null ) {
126
- return new File (pathOrUrl );
127
- }
137
+ return new File (arg );
138
+ }
139
+ if (ctorArgClass .equals (String .class )) {
140
+ return arg ;
128
141
}
129
142
if (ctorArgClass .equals (Appendable .class )) {
130
- if (pathOrUrl != null ) {
131
- return new UTF8OutputStreamWriter (new URLOutputStream (toURL (pathOrUrl )));
132
- } else {
133
- return defaultOutOrFailIfAlreadyUsed (formatterString );
134
- }
143
+ return new UTF8OutputStreamWriter (new URLOutputStream (toURL (arg )));
135
144
}
136
145
return null ;
137
146
}
138
147
139
- private <T > Constructor <T > findConstructor (Class <T > pluginClass , Class <?> ctorArgClass ) {
140
- try {
141
- if (ctorArgClass == null ) {
142
- return pluginClass .getConstructor ();
143
- } else {
144
- return pluginClass .getConstructor (ctorArgClass );
148
+ private <T > Constructor <T > findSingleArgConstructor (Class <T > pluginClass ) {
149
+ Constructor <T > constructor = null ;
150
+ for (Class ctorArgClass : CTOR_PARAMETERS ) {
151
+ try {
152
+ Constructor <T > candidate = pluginClass .getConstructor (ctorArgClass );
153
+ if (constructor != null ) {
154
+ throw new CucumberException (String .format ("Plugin %s should only define a single one-argument constructor" , pluginClass .getName ()));
155
+ }
156
+ constructor = candidate ;
157
+ } catch (NoSuchMethodException ignore ) {
145
158
}
146
- } catch (NoSuchMethodException e ) {
159
+ }
160
+ return constructor ;
161
+ }
162
+
163
+ private <T > Constructor <T > findEmptyConstructor (Class <T > pluginClass ) {
164
+ try {
165
+ return pluginClass .getConstructor ();
166
+ } catch (NoSuchMethodException ignore ) {
147
167
return null ;
148
168
}
149
169
}
@@ -201,7 +221,7 @@ public static boolean isSummaryPrinterName(String name) {
201
221
}
202
222
203
223
private static Class getPluginClass (String name ) {
204
- Matcher pluginWithFile = PLUGIN_WITH_FILE_PATTERN .matcher (name );
224
+ Matcher pluginWithFile = PLUGIN_WITH_ARGUMENT_PATTERN .matcher (name );
205
225
String pluginName ;
206
226
if (pluginWithFile .matches ()) {
207
227
pluginName = pluginWithFile .group (1 );
0 commit comments