Path: blob/master/test/hotspot/jtreg/vmTestbase/nsk/share/ArgumentParser.java
40948 views
/*1* Copyright (c) 2001, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223package nsk.share;2425import nsk.share.test.StressOptions;2627import java.util.ArrayList;28import java.util.Properties;2930/**31* Parser for JDI test's command-line arguments.32* <p>33* Test's command line may contain two kind of arguments, namely:34* <ul>35* <li> options for ArgumentParser36* <li> other arguments for the test itself37* </ul>38* <p>39* We call <i>raw arguments</i> the <code>args[]</code> array40* passed to the test's method <code>main(String args[])</code>.41* ArgumentParser instance initialized with raw arguments serves to parse42* these two kinds of arguments. Use <code>ArgumentParser(args[])</code>43* constructor, or <code>setRawArguments(args[])</code> method44* to initialize a ArgumentParser instance with particular raw arguments.45* <p>46* Arguments, started with ``<code>-</code>'' symbol are called <i>options</i>.47* They are recognized by ArgumentParser and are used by support classes48* (such as Log, Binder, etc.).49* These options should be specified in the following general form:50* <ul>51* <li> <code>-option=<i>value</i></code>52* </ul>53* or54* <ul>55* <li> <code>-option <i>value</i></code>56* </ul>57* List of the recognized options with their values may be obtained by58* invoking method <code>getOptions()</code> that returns59* a <code>Properties</code> object with options values.60* It is not recommended to get options value directly. An appropriate methods61* such as <code>verbose()</code>, <code>getArch()</code>, etc. should be used62* instead.63* Options may appear in the test command line in any order.64* <p>65* All the other arguments of command line are called <i>test arguments</i>66* (or simply <i>arguments</i>). These arguments should be handled by test itself.67* Full list of the test arguments in the same order as they appears in the command line68* may be obtained by invoking method <code>getArguments()</code>.69* <p>70* Following is the list of basic options accepted by ArgumentParser:71* <ul>72* <li> <code>-arch=</code><<i>${ARCH}</i>> -73* architecture name74* <li> <code>-waittime=</code><<i>minutes</i>> -75* timeout in minutes for waiting events or so76* <li> <code>-verbose</code> -77* verbose Log mode (default is quiet)78* <li> <code>-trace.time</code> -79* prefix log messages with timestamps (default is no)80* </ul>81* Also ArgumentParser supports following stress options (see nsk.share.test.StressOptions for details):82* <ul>83* <li> <code>-stressTime</code>84* <li> <code>-stressIterationsFactor</code>85* <li> <code>-stressThreadsFactor</code>86* <li> <code>-stressDebug</code>87* </ul>88* <p>89* Note that the tests from the particular suites have its own argument handlers90* which accepts additional options.91*92* @see #setRawArguments(String[])93* @see #getRawArguments()94* @see #getArguments()95* @see #getOptions()96* @see nsk.share.jpda.DebugeeArgumentHandler97* @see nsk.share.jdwp.ArgumentHandler98* @see nsk.share.jdi.ArgumentHandler99* @see nsk.share.jvmti.ArgumentHandler100* @see nsk.monitoring.share.ArgumentHandler101*/102public class ArgumentParser {103/**104* Raw array of command-line arguments.105*106* @see #setRawArguments(String[])107* @see #getRawArguments()108*/109protected String[] rawArguments = null;110111/**112* Refined arguments -- raw arguments but options.113*114* @see #options115* @see #getArguments()116*/117protected String[] arguments = null;118119/**120* Recognized options for ArgumentParser class.121*122* @see #arguments123* @see #getOptions()124*/125protected Properties options = new Properties();126127/**128* Make new ArgumentParser object with default values of options.129* This constructor is used only to obtain default values of options.130*131* @see #setRawArguments(String[])132*/133protected ArgumentParser() {134this(new String[0]);135}136137/**138* Keep a copy of raw command-line arguments and parse them;139* but throw an exception on parsing error.140*141* @param args Array of the raw command-line arguments.142* @throws BadOption If option values are invalid.143* @see #setRawArguments(String[])144* @see BadOption145*/146public ArgumentParser(String[] args) {147ArrayList<String> list = new ArrayList<>(args.length);148for (int i = 0; i < args.length; ++i) {149StringBuilder arg = new StringBuilder(args[i]);150// jtreg splits the command string into arguments by space symbol151// and doesn't keep arguments within double quotes as one argument,152// so we need to join them back153long doubleQuotes = numberOfDoubleQuotes(args[i]);154while (i < args.length - 1 && (doubleQuotes % 2) != 0) {155arg.append(" ").append(args[++i]);156doubleQuotes += numberOfDoubleQuotes(args[i]);157}158if (doubleQuotes % 2 != 0) {159throw new TestBug("command-line has odd number of double quotes:" + String.join(" ", args));160}161162list.add(arg.toString());163}164setRawArguments(list.toArray(String[]::new));165}166167private static long numberOfDoubleQuotes(String s) {168return s.chars().filter(c -> c == '"').count();169}170171/**172* Return a copy of the raw command-line arguments kept by173* this ArgumentParser instance.174*175* @throws NullPointerException If raw arguments were not176* set for this instance.177* @see #setRawArguments(String[])178*/179public String[] getRawArguments() {180return rawArguments.clone();181}182183/**184* Return given raw command-line argument.185*186* @param index index of argument187* @return value of raw argument188*/189public String getRawArgument(int index) {190return rawArguments[index];191}192193/**194* Return refined array of test arguments (only those of the raw195* arguments which are not recognized as options for ArgumentParser).196*197* <p>Note, that syntax of test arguments was not checked;198* while syntax of arguments describing ArgumentParser's options199* was checked while raw arguments were set to this ArgumentParser200* instance.201*202* @throws NullPointerException If raw arguments were not203* set for this instance.204* @see #setRawArguments(String[])205* @see #getOptions()206*/207public String[] getArguments() {208return arguments.clone();209}210211/**212* Return list of recognized options with their values in the form of213* <code>Properties</code> object.214* If no options has been recognized, this list will be empty.215*216* @see #setRawArguments(String[])217* @see #getArguments()218*/219public Properties getOptions() {220return (Properties) options.clone();221}222223/**224* Join specified arguments into one line using given quoting225* and separator symbols.226*227* @param args Array of the command-line arguments228* @param quote Symbol used to quote each argument229* @param separator Symbol used as separator between arguments230* @return Single line with arguments231*/232static public String joinArguments(String[] args, String quote, String separator) {233if (args.length <= 0) {234return "";235}236StringBuilder line = new StringBuilder(quote).append(args[0]).append(quote);237for (int i = 1; i < args.length; i++) {238line.append(separator).append(quote).append(args[i]).append(quote);239}240return line.toString();241}242243/**244* Join specified arguments into one line using given quoting symbol245* and space as a separator symbol.246*247* @param args Array of the command-line arguments248* @param quote Symbol used to quote each argument249* @return Single line with arguments250*/251static public String joinArguments(String[] args, String quote) {252return joinArguments(args, quote, " ");253}254255/**256* Keep a copy of command-line arguments and parse them;257* but throw an exception on parsing error.258*259* @param args Array of the raw command-line arguments.260* @throws BadOption If an option has invalid value.261* @see #getRawArguments()262* @see #getArguments()263*/264public void setRawArguments(String[] args) {265this.rawArguments = args.clone();266parseArguments();267}268269/**270* Add or replace given option value in options list and in raw arguments list.271* Use specified <code>rawPrefix</code> while adding to raw arguments list.272*273* @see #getRawArguments()274* @see #getOptions()275*/276public void setOption(String rawPrefix, String name, String value) {277String prefix = rawPrefix + name + "=";278String arg = prefix + value;279280options.setProperty(name, value);281282int length = rawArguments.length;283boolean found = false;284for (int i = 0; i < length; i++) {285if (rawArguments[i].startsWith(prefix)) {286found = true;287rawArguments[i] = arg;288break;289}290}291292if (!found) {293String[] newRawArguments = new String[length + 1];294System.arraycopy(rawArguments, 0, newRawArguments, 0, length);295newRawArguments[length] = arg;296rawArguments = newRawArguments;297}298}299300/**301* Return current architecture name from ArgumentParser's302* options.303*304* <p>Note that null string is returning if test argument305* <code>-arch</code> has not been set.306*307* @see #setRawArguments(String[])308*/309public String getArch() {310return options.getProperty("arch");311}312313/**314* Timeout (in minutes) for test's critical section like:315* (a) awaiting for an event, or conversely (b) making sure316* that there is no unexpected event.317*318* <p>By default, <i>2</i> minutes is returned if option319* <code>-waittime</code> is not set with command line.320*321* @see TimeoutHandler322*/323public int getWaitTime() {324String val = options.getProperty("waittime", "2");325int minutes;326try {327minutes = Integer.parseInt(val);328} catch (NumberFormatException e) {329throw new TestBug("Not integer value of \"waittime\" argument: " + val);330}331return minutes;332}333334/**335* Return boolean value of current Log mode:336* <ul>337* <li><i>true</i> if Log mode is verbose.338* <li><i>false</i> otherwise.339*340* <p>Note that default Log mode is quiet if test argument341* <code>-verbose</code> has not been set.342*343* @see #setRawArguments(String[])344*/345public boolean verbose() {346return options.getProperty("verbose") != null;347}348349/**350* Return boolean value of setting of timestamp for log messages:351* <ul>352* <li><i>true</i> if Log messages are timestamp'ed.353* <li><i>false</i> otherwise.354*355* <p>Note that by default Log messages won't be timestamp'ed until356* <code>-trace.time</code> has not been set.357*358* @see #setRawArguments(String[])359*/360public boolean isTimestamp() {361return options.getProperty("trace.time") != null;362}363364/**365* Return level of printing tracing messages for debugging purpose.366* Level <i>0</i> means no tracing messages at all.367*368* <p>Note that by default no tracing messages will be printed out369* until <code>-trace.level</code> has not been set.370*371* @see #setRawArguments(String[])372*/373public int getTraceLevel() {374String value = options.getProperty("trace.level", Integer.toString(Log.TraceLevel.DEFAULT));375try {376return Integer.parseInt(value);377} catch (NumberFormatException e) {378throw new Failure("Not integer value of -trace.level option: " + value);379}380}381382/**383* Parse arguments from rawArguments, extract recognized options,384* check legality of options values options and store non-option385* arguments.386*387* @throws NullPointerException If raw arguments were not set388* for this ArgumentParser instance.389* @throws BadOption If Option name is not accepted or390* option has illegal value.391* @see #setRawArguments(String[])392* @see #checkOption(String, String)393* @see #checkOptions()394*/395protected void parseArguments() {396String[] selected = new String[rawArguments.length];397Properties properties = new Properties();398int count = 0;399for (int i = 0; i < rawArguments.length; i++) {400String argument = rawArguments[i];401if (argument.startsWith("-")) {402int pos = argument.indexOf("=", 1);403String option, value;404if (pos < 0) {405option = argument.substring(1);406if (i + 1 < rawArguments.length && !rawArguments[i + 1].startsWith("-")) {407value = rawArguments[i + 1];408++i;409} else {410value = "";411}412} else {413option = argument.substring(1, pos);414value = argument.substring(pos + 1);415}416417if (!checkOption(option, value)) {418throw new BadOption("Unrecognized command line option: " + argument);419}420properties.setProperty(option, value);421} else {422selected[count++] = rawArguments[i];423}424}425// Strip away the dummy tail of the selected[] array:426arguments = new String[count];427System.arraycopy(selected, 0, arguments, 0, count);428options = properties;429checkOptions();430}431432public StressOptions getStressOptions() {433return new StressOptions(rawArguments);434}435436/**437* Check if the specified option is allowed and has legal value.438* <p>439* Derived classes for handling test arguments in particular sub-suites440* override this method to allow to accept sub-suite specific options.441* However, they should invoke this method of the base class to ensure442* that the basic options will be accepted too.443*444* @return <i>true</i> if option is allowed and has legal value445* <i>false</i> if option is unknown446* @throws BadOption If value of the allowed option is illegal.447* @see #setRawArguments(String[])448* @see #parseArguments()449*/450protected boolean checkOption(String option, String value) {451// accept arguments of nsk.share.test.StressOptions452if (StressOptions.isValidStressOption(option))453return true;454455// options with any string value456if (option.equals("arch")) {457return true;458}459460// options with positive integer value461if (option.equals("waittime")462|| option.equals("trace.level")) {463try {464int number = Integer.parseInt(value);465if (number < 0) {466throw new BadOption(option + ": value must be a positive integer");467}468} catch (NumberFormatException e) {469throw new BadOption(option + ": value must be an integer");470}471return true;472}473474// options without any value475if (option.equals("verbose")476|| option.equals("vbs")477|| option.equals("trace.time")) {478if (!(value == null || value.length() <= 0)) {479throw new BadOption(option + ": no value must be specified");480}481return true;482}483484return false;485}486487/**488* Check that the value of all options are not inconsistent.489* This method is invoked by <code>parseArguments()</code>490*491* @throws BadOption If value of the options are inconsistent492* @see #parseArguments()493*/494protected void checkOptions() {495// do nothing496}497498/**499* Thrown if invalid option or option value is found.500*/501public static class BadOption extends IllegalArgumentException {502/**503* Explain the reason.504*505* @param message Printing message.506*/507public BadOption(String message) {508super(message);509}510}511}512513514