Skip to content

Commit c6e471c

Browse files
committed
Allow String parameter in plugin constructors
1 parent 19fd08e commit c6e471c

File tree

3 files changed

+121
-59
lines changed

3 files changed

+121
-59
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ Please see [CONTRIBUTING.md](https://github.com/cucumber/cucumber/blob/master/CO
33
## [2.0.2-SNAPSHOT](https://github.com/cucumber/cucumber-jvm/compare/v2.0.1...master) (In Git)
44

55
### Added
6+
* [Core] Allow String parameter in plugin constructors (Aslak Hellesøy)
67
* [Core] Optimize MethodScanner ([#1238](https://github.com/cucumber/cucumber-jvm/pull/1236) Łukasz Suski)
8+
79
### Changed
810
* [Core] Use gherkin 5.0.0 ([#1252](https://github.com/cucumber/cucumber-jvm/commit/5e305951026a1573ede77e05e86bbe8ed3bca55b) M.P. Korstanje)
911

core/src/main/java/cucumber/runtime/formatter/PluginFactory.java

+79-59
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
import java.net.URISyntaxException;
2020
import java.net.URL;
2121
import java.util.HashMap;
22-
import java.util.Map;
2322
import java.util.regex.Matcher;
2423
import java.util.regex.Pattern;
2524

@@ -30,13 +29,21 @@
3029
* This class creates plugin instances from a String.
3130
* <p>
3231
* 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>
3542
*
3643
* @see Plugin
3744
*/
3845
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};
4047

4148
private static final HashMap<String, Class<? extends Plugin>> PLUGIN_CLASSES = new HashMap<String, Class<? extends Plugin>>() {{
4249
put("null", NullFormatter.class);
@@ -51,7 +58,7 @@ public final class PluginFactory {
5158
put("default_summary", DefaultSummaryPrinter.class);
5259
put("null_summary", NullSummaryPrinter.class);
5360
}};
54-
private static final Pattern PLUGIN_WITH_FILE_PATTERN = Pattern.compile("([^:]+):(.*)");
61+
private static final Pattern PLUGIN_WITH_ARGUMENT_PATTERN = Pattern.compile("([^:]+):(.*)");
5562
private String defaultOutFormatter = null;
5663

5764
private Appendable defaultOut = new PrintStream(System.out) {
@@ -62,88 +69,101 @@ public void close() {
6269
};
6370

6471
public Plugin create(String pluginString) {
65-
Matcher pluginWithFile = PLUGIN_WITH_FILE_PATTERN.matcher(pluginString);
72+
Matcher pluginWithArgument = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(pluginString);
6673
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);
7178
} else {
7279
pluginName = pluginString;
80+
argument = null;
7381
}
7482
Class<? extends Plugin> pluginClass = pluginClass(pluginName);
7583
try {
76-
return instantiate(pluginString, pluginClass, path);
84+
return instantiate(pluginString, pluginClass, argument);
7785
} catch (IOException e) {
7886
throw new CucumberException(e);
7987
} catch (URISyntaxException e) {
8088
throw new CucumberException(e);
8189
}
8290
}
8391

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);
106100
}
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)));
108108
}
109109

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());
113119
}
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;
117128
}
118129
}
130+
if (ctorArgClass.equals(URI.class)) {
131+
return new URI(arg);
132+
}
119133
if (ctorArgClass.equals(URL.class)) {
120-
if (pathOrUrl != null) {
121-
return toURL(pathOrUrl);
122-
}
134+
return toURL(arg);
123135
}
124136
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;
128141
}
129142
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)));
135144
}
136145
return null;
137146
}
138147

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) {
145158
}
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) {
147167
return null;
148168
}
149169
}
@@ -201,7 +221,7 @@ public static boolean isSummaryPrinterName(String name) {
201221
}
202222

203223
private static Class getPluginClass(String name) {
204-
Matcher pluginWithFile = PLUGIN_WITH_FILE_PATTERN.matcher(name);
224+
Matcher pluginWithFile = PLUGIN_WITH_ARGUMENT_PATTERN.matcher(name);
205225
String pluginName;
206226
if (pluginWithFile.matches()) {
207227
pluginName = pluginWithFile.group(1);

core/src/test/java/cucumber/runtime/formatter/PluginFactoryTest.java

+40
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,33 @@ public void instantiates_custom_file_plugin() throws IOException {
153153
assertEquals(new File("halp.txt"), plugin.out);
154154
}
155155

156+
@Test
157+
public void instantiates_custom_string_arg_plugin() throws IOException {
158+
WantsString plugin = (WantsString) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsString:hello");
159+
assertEquals("hello", plugin.arg);
160+
}
161+
162+
@Test
163+
public void instantiates_plugin_using_empty_constructor_when_unspecified() throws IOException {
164+
WantsStringOrDefault plugin = (WantsStringOrDefault) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsStringOrDefault");
165+
assertEquals("defaultValue", plugin.arg);
166+
}
167+
168+
@Test
169+
public void instantiates_plugin_using_arg_constructor_when_specified() throws IOException {
170+
WantsStringOrDefault plugin = (WantsStringOrDefault) fc.create("cucumber.runtime.formatter.PluginFactoryTest$WantsStringOrDefault:hello");
171+
assertEquals("hello", plugin.arg);
172+
}
173+
156174
public static class WantsAppendable extends StubFormatter {
157175
public final Appendable out;
158176

159177
public WantsAppendable(Appendable out) {
160178
this.out = out;
161179
}
180+
public WantsAppendable() {
181+
this.out = null;
182+
}
162183
}
163184

164185
public static class WantsUrl extends StubFormatter {
@@ -184,4 +205,23 @@ public WantsFile(File out) {
184205
this.out = out;
185206
}
186207
}
208+
209+
public static class WantsString extends StubFormatter {
210+
public final String arg;
211+
212+
public WantsString(String arg) {
213+
this.arg = arg;
214+
}
215+
}
216+
217+
public static class WantsStringOrDefault extends StubFormatter {
218+
public final String arg;
219+
220+
public WantsStringOrDefault(String arg) {
221+
this.arg = arg;
222+
}
223+
public WantsStringOrDefault() {
224+
this("defaultValue");
225+
}
226+
}
187227
}

0 commit comments

Comments
 (0)