Obrada argumenata naredbenog retka u Javi: Slučaj zatvoren

Mnoge Java aplikacije pokrenute iz naredbenog retka uzimaju argumente za kontrolu svog ponašanja. Ti su argumenti dostupni u argumentu niza niza proslijeđenom u statičku main()metodu aplikacije . Obično postoje dvije vrste argumenata: opcije (ili prekidači) i stvarni argumenti podataka. Java aplikacija mora obraditi ove argumente i izvršiti dva osnovna zadatka:

  1. Provjerite je li korištena sintaksa valjana i podržana
  2. Dohvatite stvarne podatke potrebne aplikaciji za obavljanje njezinih operacija

Kôd koji izvršava ove zadatke često je izrađen po mjeri za svaku aplikaciju i stoga zahtijeva znatan napor kako za stvaranje tako i za održavanje, pogotovo ako zahtjevi nadilaze jednostavne slučajeve sa samo jednom ili dvije mogućnosti. OptionsKlase opisane u ovom članku implementira generički pristup lako rukovati i najsloženije situacije. Klasa omogućuje jednostavnu definiciju potrebnih opcija i argumenata podataka, te pruža temeljite provjere sintakse i jednostavan pristup rezultatima tih provjera. Za ovaj su projekt korištene i nove značajke Java 5, poput generičkih i typesafe enuma.

Vrste argumenata naredbenog retka

Tijekom godina napisao sam nekoliko Java alata koji uzimaju argumente naredbenog retka kako bi kontrolirali svoje ponašanje. Rano mi je smetalo ručno stvaranje i održavanje koda za obradu različitih opcija. To je dovelo do razvoja prototipa klase kako bi se olakšao ovaj zadatak, ali ta je klasa doduše imala svoja ograničenja jer se, pomnim pregledom, pokazalo da je značajan broj mogućih različitih sorti za argumente naredbenog retka. Na kraju sam odlučio razviti opće rješenje ovog problema.

Razvijajući ovo rješenje, morao sam riješiti dva glavna problema:

  1. Utvrdite sve sorte u kojima se mogu pojaviti opcije naredbenog retka
  2. Pronađite jednostavan način da dopustite korisnicima da izraze ove sorte kada koriste klasu koja se tek treba razviti

Analiza problema 1 dovela je do sljedećih opažanja:

  • Opcije naredbenog retka suprotno argumentima podataka naredbenog retka - započnite s prefiksom koji ih jedinstveno identificira. Primjeri prefiksa uključuju crticu ( -) na Unix platformama za opcije poput -aili kosu crtu ( /) na Windows platformama.
  • Opcije mogu biti jednostavni prekidači (tj. -aMogu biti prisutni ili ne) ili uzeti vrijednost. Primjer je:

    java MyTool -a -b logfile.inp 
  • Opcije koje uzimaju vrijednost mogu imati različite separatore između stvarnog ključa opcije i vrijednosti. Takvi separatori mogu biti prazan prostor, dvotačka ( :) ili znak jednakosti ( =):

    java MyTool -a -b logfile.inp java MyTool -a -b: logfile.inp java MyTool -a -b = logfile.inp 
  • Opcije koje uzimaju vrijednost mogu dodati još jednu razinu složenosti. Razmotrimo način na koji Java podržava definiciju svojstava okoline kao primjer:

    java -Djava.library.path = / usr / lib ... 
  • Dakle, osim stvarne tipke opcije ( D), separatora ( =) i stvarne vrijednosti opcije ( /usr/lib), dodatni parametar ( java.library.path) može poprimiti bilo koji broj vrijednosti (u gornjem primjeru pomoću ove sintakse mogu se navesti brojna svojstva okoline ). U ovom se članku ovaj parametar naziva "detalj".
  • Opcije također imaju svojstvo višestrukosti: mogu biti potrebne ili neobavezne, a broj dopuštenih zahtjeva također se može razlikovati (poput točno jednom, jednom ili više puta ili drugih mogućnosti).
  • Argumenti podataka su svi argumenti naredbenog retka koji ne počinju s prefiksom. Ovdje prihvatljivi broj takvih argumenata podataka može varirati između minimalnog i maksimalnog broja (koji nisu nužno isti). Osim toga, obično aplikacija zahtijeva da ovi argumenti podataka budu posljednji u naredbenom retku, ali to ne mora uvijek biti slučaj. Na primjer:

    java MyTool -a -b = logfile.inp data1 data2 data3 // Svi podaci na kraju 

    ili

    java MyTool -a data1 data2 -b = logfile.inp data3 // Može biti prihvatljivo za aplikaciju 
  • Složenije aplikacije mogu podržati više od jednog skupa opcija:

    java MyTool -a -b datafile.inp java MyTool -k [-verbose] foo bar duh java MyTool -provjeri -verify logfile.out 
  • Napokon, aplikacija se može odlučiti zanemariti bilo koju nepoznatu opciju ili bi takve opcije mogla smatrati pogreškom.

Dakle, osmišljavajući način da dopustim korisnicima da izraze sve ove sorte, smislio sam sljedeći obrazac s općim opcijama, koji je korišten kao osnova za ovaj članak:

[[]] 

Ovaj obrazac mora se kombinirati sa svojstvom množine kako je gore opisano.

Unutar ograničenja općeg oblika gore opisane opcije, Optionsklasa opisana u ovom članku osmišljena je kao opće rješenje za sve potrebe obrade naredbenog retka koje Java aplikacija može imati.

Razredi pomoćnika

OptionsKlasa, koja je srž klasa za rješenje opisanog u ovom članku, dolazi s dvije helper klase:

  1. OptionData: Ova klasa sadrži sve podatke za jednu određenu opciju
  2. OptionSet: Ovaj razred sadrži niz opcija. Optionssam može sadržavati bilo koji broj takvih skupova

Prije opisivanja pojedinosti ove nastave, Optionsmoraju se predstaviti drugi važni pojmovi nastave.

Sigurni popisi

Prefiks, separator i svojstvo množine zabilježeni su enumima, značajkom koju je Java 5 prvi put omogućila:

javni enumski prefiks {DASH ('-'), SLASH ('/'); privatni char c; privatni prefiks (char c) {this.c = c; } char getName () {return c; }} javni separator za nabrajanje {COLON (':'), EQUALS ('='), BLANK (''), NONE ('D'); privatni char c; privatni separator (char c) {this.c = c; } char getName () {return c; }} javni nabroj Mnogostrukost {ONCE, ONCE_OR_MORE, ZERO_OR_ONE, ZERO_OR_MORE; }

Korištenje enuma ima neke prednosti: povećana sigurnost tipa i čvrsta kontrola skupa dopuštenih vrijednosti bez napora. Enumi se također mogu prikladno koristiti s generiziranim zbirkama.

Imajte na umu da Prefixand i Separatornabrajanja imaju vlastite konstruktore, omogućujući definiciju stvarnog znaka koji predstavlja ovu nabrojanu instancu (nasuprot imenu koji se koristi za upućivanje na određeni primjerak nabrajanja). Ti se znakovi mogu dohvatiti getName()metodama ovih nabrajanja , a znakovi se koriste za java.util.regexsintaksu uzorka paketa. Ovaj se paket koristi za izvođenje nekih provjera sintakse u Optionsklasi, čiji će detalji slijediti.

MultiplicityEnum trenutno podržava četiri različite vrijednosti:

  1. ONCE: Opcija se mora dogoditi točno jednom
  2. ONCE_OR_MORE: The option has to occur at least once
  3. ZERO_OR_ONCE: The option can either be absent or present exactly once
  4. ZERO_OR_MORE: The option can either be absent or present any number of times

More definitions can easily be added should the need arise.

The OptionData class

The OptionData class is basically a data container: firstly, for the data describing the option itself, and secondly, for the actual data found on the command line for that option. This design is already reflected in the constructor:

OptionData(Options.Prefix prefix, String key, boolean detail, Options.Separator separator, boolean value, Options.Multiplicity multiplicity) 

The key is used as the unique identifier for this option. Note that these arguments directly reflect the findings described earlier: a full option description must have at least a prefix, a key, and multiplicity. Options taking a value also have a separator and might accept details. Note also that this constructor has package access, so applications cannot directly use it. Class OptionSet's addOption() method adds the options. This design principle has the advantage that we have much better control on the actual possible combinations of arguments used to create OptionData instances. For example, if this constructor were public, you could create an instance with detail set to true and value set to false, which is of course nonsense. Rather than having elaborate checks in the constructor itself, I decided to provide a controlled set of addOption() methods.

The constructor also creates an instance of java.util.regex.Pattern, which is used for this option's pattern-matching process. One example would be the pattern for an option taking a value, no details, and a nonblank separator:

pattern = java.util.regex.Pattern.compile(prefix.getName() + key + separator.getName() + "(.+)$"); 

The OptionData class, as already mentioned, also holds the results of the checks performed by the Options class. It provides the following public methods to access these results:

int getResultCount() String getResultValue(int index) String getResultDetail(int index) 

The first method, getResultCount(), returns the number of times an option was found. This method design directly ties in with the multiplicity defined for the option. For options taking a value, this value can be retrieved using the getResultValue(int index) method, where the index can range between 0 and getResultCount() - 1. For value options that also accept details, these can be similarly accessed using the getResultDetail(int index) method.

The OptionSet class

The OptionSet class is basically a container for a set of OptionData instances and also the data arguments found on the command line.

The constructor has the form:

OptionSet(Options.Prefix prefix, Options.Multiplicity defaultMultiplicity, String setName, int minData, int maxData) 

Again, this constructor has package access. Option sets can only be created through the Options class's different addSet() methods. The default multiplicity for the options specified here can be overridden when adding an option to the set. The set name specified here is a unique identifier used to refer to the set. minData and maxData are the minimum and maximum number of acceptable data arguments for this set.

The public API for OptionSet contains the following methods:

General access methods:

String getSetName() int getMinData() int getMaxData() 

Methods to add options:

OptionSet addOption(String key) OptionSet addOption(String key, Multiplicity multiplicity) OptionSet addOption(String key, Separator separator) OptionSet addOption(String key, Separator separator, Multiplicity multiplicity) OptionSet addOption(String key, boolean details, Separator separator) OptionSet addOption(String key, boolean details, Separator separator, Multiplicity multiplicity) 

Methods to access check result data:

java.util.ArrayList getOptionData() OptionData getOption(String key) boolean isSet(String key) java.util.ArrayList getData() java.util.ArrayList getUnmatched() 

Note that the methods for adding options that take a Separator argument create an OptionData instance accepting a value. The addOption() methods return the set instance itself, which allows invocation chaining:

Options options = new Options(args); options.addSet("MySet").addOption("a").addOption("b"); 

After the checks have been performed, their results are available through the remaining methods. getOptionData() returns a list of all OptionData instances, while getOption() allows direct access to a specific option. isSet(String key) is a convenience method that checks whether an options was found at least once on the command line. getData() provides access to the data arguments found, while getUnmatched() lists all options found on the command line for which no matching OptionData instances were found.

The Options class

Options is the core class with which applications will interact. It provides several constructors, all of which take the command line argument string array that the main() method provides as the first argument:

Options(String args[]) Options(String args[], int data) Options(String args[], int defMinData, int defMaxData) Options(String args[], Multiplicity defaultMultiplicity) Options(String args[], Multiplicity defaultMultiplicity, int data) Options(String args[], Multiplicity defaultMultiplicity, int defMinData, int defMaxData) Options(String args[], Prefix prefix) Options(String args[], Prefix prefix, int data) Options(String args[], Prefix prefix, int defMinData, int defMaxData) Options(String args[], Prefix prefix, Multiplicity defaultMultiplicity) Options(String args[], Prefix prefix, Multiplicity defaultMultiplicity, int data) Options(String args[], Prefix prefix, Multiplicity defaultMultiplicity, int defMinData, int defMaxData) 

The first constructor in this list is the simplest one using all the default values, while the last one is the most generic.

Table 1: Arguments for the Options() constructors and their meaning

Value Description Default
prefix This constructor argument is the only place where a prefix can be specified. This value is passed on to any option set and any option created subsequently. The idea behind this approach is that within a given application, it proves unlikely that different prefixes will need to be used. Prefix.DASH
defaultMultiplicity This default multiplicity is passed to each option set and used as the default for options added to a set without specifying a multiplicity. Of course, this multiplicity can be overridden for each option added. Multiplicity.ONCE
defMinData defMinData is the default minimum number of supported data arguments passed to each option set, but it can of course be overridden when adding a set. 0
defMaxData defMaxData je zadani maksimalni broj podržanih argumenata podataka prosljeđenih svakom skupu opcija, ali to se naravno može nadjačati prilikom dodavanja skupa. 0