-
Notifications
You must be signed in to change notification settings - Fork 338
/
PhantomJSDriverService.java
591 lines (527 loc) · 25.6 KB
/
PhantomJSDriverService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
/*
This file is part of the GhostDriver by Ivan De Marino <http://ivandemarino.me>.
Copyright (c) 2012-2014, Ivan De Marino <http://ivandemarino.me>
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.openqa.selenium.phantomjs;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.Proxy;
import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.net.PortProber;
import org.openqa.selenium.os.ExecutableFinder;
import org.openqa.selenium.remote.service.DriverService;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
import java.util.logging.Logger;
import static com.google.common.base.Preconditions.*;
/**
* Service that controls the life-cycle of a PhantomJS in Remote WebDriver mode.
* The Remote WebDriver is implemented via GhostDriver.
*
*
* @author Ivan De Marino http://ivandemarino.me
*/
public class PhantomJSDriverService extends DriverService {
/**
* Internal Logger
*/
private static final Logger LOG = Logger.getLogger(PhantomJSDriverService.class.getName());
/**
* System property/capability that defines the location of the PhantomJS executable.
*/
public static final String PHANTOMJS_EXECUTABLE_PATH_PROPERTY = "phantomjs.binary.path";
/**
* Optional System property/capability that defines the location of the
* GhostDriver JavaScript launch file (i.e. {@code "src/main.js"}).
*/
public static final String PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY = "phantomjs.ghostdriver.path";
/**
* Optional System property that defines the location of the
* GhostDriver logfile path.
*/
public static final String PHANTOMJS_LOGFILE_PATH_PROPERTY = "phantomjs.logfile.path";
/**
* Capability that allows to add custom command line arguments to the
* spawned PhantomJS process.
*
* <p>
* Set this capability with a list of of argument strings to add, e.g.
* {@code new String[] { "--ignore-ssl-errors=yes", "--load-images=no" }}.
* </p>
*/
public static final String PHANTOMJS_CLI_ARGS = "phantomjs.cli.args";
/**
* Capability that allows to pass custom command line arguments to the
* GhostDriver JavaScript launch file, spawned PhantomJS process.
* NOTE: This is useful only when used together with PhantomJSDriverService#PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY.
*
* <p>
* Set this capability with a list of of argument strings to add, e.g.
* {@code new String[] { "--logFile=PATH", "--logLevel=DEBUG" }}.
* </p>
*
* <br>
* Acceptable arguments:
* <ul>
* <li>{@code --ip=IP_GHOSTDRIVER_SHOULD_LISTEN_ON}</li>
* <li>{@code --port=PORT_GHOSTDRIVER_SHOULD_LISTEN_ON}</li>
* <li>{@code --hub=HTTP_ADDRESS_TO_SELENIUM_HUB}</li>
* <li>{@code --logFile=PATH_TO_LOGFILE}</li>
* <li>{@code --logLevel=(INFO|DEBUG|WARN|ERROR)}</li>
* <li>{@code --logColor=(false|true)}</li>
* </ul>
*/
public static final String PHANTOMJS_GHOSTDRIVER_CLI_ARGS = "phantomjs.ghostdriver.cli.args";
/**
* Set capabilities with this prefix to apply it to the PhantomJS {@code page.settings.*} object.
* Every PhantomJS WebPage Setting can be used.
* See <a href="http://phantomjs.org/api/webpage/property/settings.html">PhantomJS docs</a>.
*/
public static final String PHANTOMJS_PAGE_SETTINGS_PREFIX = "phantomjs.page.settings.";
/**
* Set capabilities with this prefix to apply it to the PhantomJS {@code page.customHeaders.*} object.
* Any header can be used.
* See <a href="http://phantomjs.org/api/webpage/property/custom-headers.html">PhantomJS docs</a>.
*/
public static final String PHANTOMJS_PAGE_CUSTOMHEADERS_PREFIX = "phantomjs.page.customHeaders.";
/**
* Capability that allows to access to those sites using self-signed or invalid certificates, and where the certificate
* does not match the serving domain as if the HTTPS was configured properly.
*/
public static final String ACCEPT_SSL_CERTS = "acceptSslCerts";
/**
* Default Log file name.
* Can be changed using {@link PhantomJSDriverService.Builder#withLogFile(java.io.File)}.
*/
private static final String PHANTOMJS_DEFAULT_LOGFILE = "phantomjsdriver.log";
private static final String PHANTOMJS_DEFAULT_EXECUTABLE = "phantomjs";
private static final String PHANTOMJS_DOC_LINK = "https://github.com/ariya/phantomjs/wiki";
private static final String PHANTOMJS_DOWNLOAD_LINK = "http://phantomjs.org/download.html";
private static final String GHOSTDRIVER_DOC_LINK = "https://github.com/detro/ghostdriver/blob/master/README.md";
private static final String GHOSTDRIVER_DOWNLOAD_LINK = "https://github.com/detro/ghostdriver/downloads";
/**
* Constructor
*
* @param executable File pointing at the PhantomJS executable.
* @param port Which port to start the PhantomJS/GhostDriver on.
* @param args The arguments to the launched server.
* @param environment The environment for the launched server.
* @throws java.io.IOException If an I/O error occurs.
*/
private PhantomJSDriverService(File executable,
int port,
ImmutableList<String> args,
ImmutableMap<String, String> environment) throws IOException {
super(executable, port, args, environment);
// Print out the parameters used to launch PhantomJS Driver Service
LOG.info("executable: " + executable.getAbsolutePath());
LOG.info("port: " + port);
LOG.info("arguments: " + args.toString());
LOG.info("environment: " + environment.toString());
}
/**
* Configures and returns a new {@link PhantomJSDriverService} using the default configuration.
*
*
* <br>
* In this configuration, the service will use the PhantomJS executable identified by the the
* following capability, system property or PATH environment variables:
* <ul>
* <li>{@link PhantomJSDriverService#PHANTOMJS_EXECUTABLE_PATH_PROPERTY}</li>
* <li>
* {@link PhantomJSDriverService#PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY}
* (Optional - without will use GhostDriver internal to PhantomJS)
* </li>
* </ul>
* <br>
* Each service created by this method will be configured to find and use a free port on the current system.
* <br>
* @param desiredCapabilities desired capabilities
*
* @return A new ChromeDriverService using the default configuration.
*/
public static PhantomJSDriverService createDefaultService(Capabilities desiredCapabilities) {
// Look for Proxy configuration within the Capabilities
Proxy proxy = null;
if (desiredCapabilities != null) {
proxy = Proxy.extractFrom(desiredCapabilities);
}
// Find PhantomJS executable
File phantomjsfile = findPhantomJS(desiredCapabilities, PHANTOMJS_DOC_LINK, PHANTOMJS_DOWNLOAD_LINK);
// Find GhostDriver main JavaScript file
File ghostDriverfile = findGhostDriver(desiredCapabilities, GHOSTDRIVER_DOC_LINK, GHOSTDRIVER_DOWNLOAD_LINK);
// Build & return service
return new Builder().usingPhantomJSExecutable(phantomjsfile)
.usingGhostDriver(ghostDriverfile)
.usingAnyFreePort()
.withProxy(proxy)
.withLogFile(findLogFile())
.withAcceptSslCerts(findAcceptSslCerts(desiredCapabilities))
.usingCommandLineArguments(
findCLIArgumentsFromCaps(desiredCapabilities, PHANTOMJS_CLI_ARGS))
.usingGhostDriverCommandLineArguments(
findCLIArgumentsFromCaps(desiredCapabilities, PHANTOMJS_GHOSTDRIVER_CLI_ARGS))
.build();
}
/**
* Same as {@link PhantomJSDriverService#createDefaultService(org.openqa.selenium.Capabilities)}.
* <br>
* In this case PhantomJS or GhostDriver can't be searched within the Capabilities, only System
* Properties.
*
* @return A new ChromeDriverService using the default configuration.
*/
public static PhantomJSDriverService createDefaultService() {
return createDefaultService(null);
}
/**
* Looks into the Capabilities, the current $PATH and the System Properties for
* {@link PhantomJSDriverService#PHANTOMJS_EXECUTABLE_PATH_PROPERTY}.
* <br>
* NOTE: If the Capability, the $PATH and the System Property are set, the Capability takes
* priority over the System Property, that in turn takes priority over the $PATH.
*
* @param desiredCapabilities Capabilities in which we will look for the path to PhantomJS
* @param docsLink The link to the PhantomJS documentation page
* @param downloadLink The link to the PhantomJS download page
* @return The driver executable as a {@link File} object
* @throws IllegalStateException If the executable not found or cannot be executed
*/
protected static File findPhantomJS(Capabilities desiredCapabilities, String docsLink,
String downloadLink) {
String phantomjspath;
if (desiredCapabilities != null &&
desiredCapabilities.getCapability(PHANTOMJS_EXECUTABLE_PATH_PROPERTY) != null) {
phantomjspath = (String) desiredCapabilities.getCapability(PHANTOMJS_EXECUTABLE_PATH_PROPERTY);
} else {
phantomjspath = new ExecutableFinder().find(PHANTOMJS_DEFAULT_EXECUTABLE);
phantomjspath = System.getProperty(PHANTOMJS_EXECUTABLE_PATH_PROPERTY, phantomjspath);
}
checkState(phantomjspath != null,
"The path to the driver executable must be set by the %s capability/system property/PATH variable;"
+ " for more information, see %s. "
+ "The latest version can be downloaded from %s",
PHANTOMJS_EXECUTABLE_PATH_PROPERTY,
docsLink,
downloadLink);
File phantomjs = new File(phantomjspath);
checkExecutable(phantomjs);
return phantomjs;
}
protected static boolean findAcceptSslCerts(Capabilities desiredCapabilities) {
if (desiredCapabilities != null &&
desiredCapabilities.getCapability(ACCEPT_SSL_CERTS) != null) {
return (Boolean) desiredCapabilities.getCapability(ACCEPT_SSL_CERTS);
}
return false;
}
/**
* Find the GhostDriver main file (i.e. {@code "main.js"}).
* <br>
* Looks into the Capabilities and the System Properties for
* {@link PhantomJSDriverService#PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY}.
* <br>
* NOTE: If both the Capability and the System Property are set, the Capability takes priority.
*
* @param desiredCapabilities Capabilities in which we will look for the path to GhostDriver
* @param docsLink The link to the GhostDriver documentation page
* @param downloadLink The link to the GhostDriver download page
* @return The driver executable as a {@link File} object
* @throws IllegalStateException If the executable not found or cannot be executed
*/
protected static File findGhostDriver(Capabilities desiredCapabilities, String docsLink, String downloadLink) {
// Recover path to GhostDriver from the System Properties or the Capabilities
String ghostdriverpath;
if (desiredCapabilities != null &&
desiredCapabilities.getCapability(PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY) != null) {
ghostdriverpath = (String) desiredCapabilities.getCapability(PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY);
} else {
ghostdriverpath = System.getProperty(PHANTOMJS_GHOSTDRIVER_PATH_PROPERTY);
}
if (ghostdriverpath != null) {
// Check few things on the file before returning it
File ghostdriver = new File(ghostdriverpath);
checkState(ghostdriver.exists(),
"The GhostDriver does not exist: %s",
ghostdriver.getAbsolutePath());
checkState(ghostdriver.isFile(),
"The GhostDriver is a directory: %s",
ghostdriver.getAbsolutePath());
checkState(ghostdriver.canRead(),
"The GhostDriver is not a readable file: %s",
ghostdriver.getAbsolutePath());
return ghostdriver;
}
// This means that no GhostDriver System Property nor Capability was set
return null;
}
private static File findLogFile() {
String logfilepath;
if (System.getProperty(PHANTOMJS_LOGFILE_PATH_PROPERTY) != null) {
logfilepath = System.getProperty(PHANTOMJS_LOGFILE_PATH_PROPERTY);
} else {
logfilepath = PHANTOMJS_DEFAULT_LOGFILE;
}
return new File(logfilepath);
}
private static String[] findCLIArgumentsFromCaps(Capabilities desiredCapabilities, String capabilityName) {
if (desiredCapabilities != null) {
Object cap = desiredCapabilities.getCapability(capabilityName);
if (cap != null) {
if (cap instanceof String[]) {
return (String[]) cap;
} else if (cap instanceof Collection) {
try {
@SuppressWarnings("unchecked")
Collection<String> capCollection = (Collection<String>)cap;
return capCollection.toArray(new String[capCollection.size()]);
} catch (Exception e) {
// If casting fails, log an error and assume no CLI arguments are provided
LOG.warning(String.format(
"Unable to set Capability '%s' as it was neither a String[] or a Collection<String>",
capabilityName));
}
}
}
}
return new String[]{}; // nothing found: return an empty array of arguments
}
/**
* Builder used to configure new {@link PhantomJSDriverService} instances.
*/
public static class Builder {
private int port = 0;
private File phantomjs = null;
private File ghostdriver = null;
private ImmutableMap<String, String> environment = ImmutableMap.of();
private File logFile;
private Proxy proxy = null;
private String[] commandLineArguments = null;
private String[] ghostdriverCommandLineArguments = null;
private boolean acceptSslCerts = false;
/**
* Sets which PhantomJS executable the builder will use.
*
* @param file The executable to use.
* @return A self reference.
*/
public Builder usingPhantomJSExecutable(File file) {
checkNotNull(file);
checkExecutable(file);
this.phantomjs = file;
return this;
}
/**
* Sets which GhostDriver the builder will use.
*
* @param file The GhostDriver's {@code main.js} to use.
* @return A self reference.
*/
public Builder usingGhostDriver(File file) {
this.ghostdriver = file;
return this;
}
/**
* Sets which port the service should listen on. A value of 0 indicates that any free port
* may be used.
*
* @param port The port to use; must be non-negative.
* @return A self reference.
*/
public Builder usingPort(int port) {
checkArgument(port >= 0, "Invalid port number: %d", port);
this.port = port;
return this;
}
/**
* Configures the service to listen on any available port.
*
* @return A self reference.
*/
public Builder usingAnyFreePort() {
this.port = 0;
return this;
}
/**
* Defines the environment for the service. These settings will be inherited by every
* browser session launched by the service.
*
* @param environment A map of the environment variables to launch the service with.
* @return A self reference.
*/
public Builder withEnvironment(Map<String, String> environment) {
this.environment = ImmutableMap.copyOf(environment);
return this;
}
/**
* Configures the service to write log to the given file.
*
* @param logFile A file to write log to.
* @return A self reference.
*/
public Builder withLogFile(File logFile) {
this.logFile = logFile;
return this;
}
/**
* Configures the service to use a specific Proxy configuration.
* <br>
* NOTE: Usually the proxy configuration is passed to the Remote WebDriver via WireProtocol
* Capabilities. PhantomJS doesn't yet support protocol configuration at runtime: it
* requires it to be defined on launch.
*
* @param proxy The {@link Proxy} configuration from the {@link org.openqa.selenium.remote.DesiredCapabilities}
* @return A self reference.
*/
public Builder withProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Configures the service to pass additional command line arguments to the PhantomJS executable.
* @param commandLineArguments list of command line arguments, e.g. "--ignore-ssl-errors=yes"
* @return A self reference.
*/
public Builder usingCommandLineArguments(String[] commandLineArguments) {
this.commandLineArguments = commandLineArguments;
return this;
}
/**
* Configures the service to pass additional command line arguments to GhostDriver, run by PhantomJS executable.
* @param ghostdriverCommandLineArguments list of command line arguments for GhostDriver
* @return A self reference.
*/
public Builder usingGhostDriverCommandLineArguments(String[] ghostdriverCommandLineArguments) {
this.ghostdriverCommandLineArguments = ghostdriverCommandLineArguments;
return this;
}
/**
* Creates a new service. Before creating a new service, the builder will find a port for
* the server to listen to.
*
* @return The new service object.
*/
public PhantomJSDriverService build() {
// Find a port to listen on, if not already decided
port = port == 0 ? PortProber.findFreePort() : port;
// Few final checks
checkState(phantomjs != null, "Path to PhantomJS executable not specified");
try {
// Build a list of command line arguments for the executable
ImmutableList.Builder<String> argsBuilder = ImmutableList.builder();
// Add command line proxy configuration for PhantomJS
if (proxy != null) {
switch (proxy.getProxyType()) {
case MANUAL:
if (proxy.getHttpProxy() != null && !proxy.getHttpProxy().isEmpty()) { //< HTTP proxy
argsBuilder.add("--proxy-type=http");
argsBuilder.add(String.format("--proxy=%s", proxy.getHttpProxy()));
} else if (proxy.getSocksProxy() != null && !proxy.getSocksProxy().isEmpty()) { //< SOCKS5 proxy
argsBuilder.add("--proxy-type=socks5");
argsBuilder.add(String.format("--proxy=%s", proxy.getSocksProxy()));
if (proxy.getSocksUsername() != null && !proxy.getSocksUsername().isEmpty()
&& proxy.getSocksPassword() != null && !proxy.getSocksPassword().isEmpty()) {
argsBuilder.add(String.format("--proxy-auth=%s:%s", proxy.getSocksUsername(),
proxy.getSocksPassword()));
}
} else {
// TODO Not supported yet by PhantomJS
checkArgument(true, "PhantomJS supports only HTTP and Socks5 Proxy currently");
}
break;
case PAC:
// TODO Not supported yet by PhantomJS
checkArgument(true, "PhantomJS doesn't support Proxy PAC files");
break;
case SYSTEM:
argsBuilder.add("--proxy-type=system");
break;
case AUTODETECT:
// TODO Not supported yet by PhantomJS
checkArgument(true, "PhantomJS doesn't support Proxy Auto-configuration");
break;
case DIRECT:
default:
argsBuilder.add("--proxy-type=none");
break;
}
}
if (this.acceptSslCerts) {
argsBuilder.add("--web-security=false");
argsBuilder.add("--ssl-protocol=any");
argsBuilder.add("--ignore-ssl-errors=true");
}
// Additional command line arguments (if provided)
if (this.commandLineArguments != null) {
argsBuilder.add(this.commandLineArguments);
}
// Should use an external GhostDriver?
if (ghostdriver != null) { //< Path to GhostDriver provided: use it simply as a PhantomJS script
// Add the canonical path to GhostDriver
argsBuilder.add(ghostdriver.getCanonicalPath());
// Add the port to listen on (if not specified in command line args)
if (!argsContains(this.ghostdriverCommandLineArguments, "port")) {
argsBuilder.add(String.format("--port=%d", port));
}
// Add Log File (if not specified in command line args)
if (logFile != null && !argsContains(this.ghostdriverCommandLineArguments, "logFile")) {
argsBuilder.add(String.format("--logFile=%s", logFile.getAbsolutePath()));
}
// Additional GhostDriver command line arguments (if provided)
if (this.ghostdriverCommandLineArguments!= null) {
argsBuilder.add(this.ghostdriverCommandLineArguments);
}
} else { //< Path to GhostDriver not provided: use PhantomJS's internal GhostDriver (default behaviour)
// Append required parameters (if not specified in command line args)
if (!argsContains(this.commandLineArguments, "webdriver")) {
argsBuilder.add(String.format("--webdriver=%d", port));
}
// Add Log File (if not specified in command line args)
if (logFile != null && !argsContains(this.commandLineArguments, "webdriver-logfile")) {
argsBuilder.add(String.format("--webdriver-logfile=%s", logFile.getAbsolutePath()));
}
}
// Create a new service
return new PhantomJSDriverService(phantomjs, port, argsBuilder.build(), environment);
} catch (IOException e) {
throw new WebDriverException(e);
}
}
public Builder withAcceptSslCerts(boolean acceptSslCerts) {
this.acceptSslCerts = acceptSslCerts;
return this;
}
private boolean argsContains(String[] args, String targetArg) {
if (args != null) {
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.startsWith("--" + targetArg + "=")) {
return true;
}
}
}
return false;
}
}
}