Programmering

Behandling af kommandolinjeargumenter i Java: Sagen er lukket

Mange Java-applikationer startet fra kommandolinjen tager argumenter for at kontrollere deres adfærd. Disse argumenter er tilgængelige i strengarrayargumentet, der sendes til programmets statiske hoved () metode. Der er typisk to typer argumenter: indstillinger (eller switche) og faktiske dataargumenter. En Java-applikation skal behandle disse argumenter og udføre to grundlæggende opgaver:

  1. Kontroller, om den anvendte syntaks er gyldig og understøttet
  2. Hent de faktiske data, der kræves for, at applikationen kan udføre dens handlinger

Ofte er koden, der udfører disse opgaver, skræddersyet til hver applikation og kræver derfor en betydelig indsats både for at oprette og vedligeholde, især hvis kravene går ud over enkle tilfælde med kun en eller to muligheder. Det Muligheder klasse beskrevet i denne artikel implementerer en generisk tilgang til let at håndtere de mest komplekse situationer. Klassen giver mulighed for en enkel definition af de krævede indstillinger og dataargumenter og giver grundige syntakschecks og let adgang til resultaterne af disse checks. Nye Java 5-funktioner som generiske og typesafe enums blev også brugt til dette projekt.

Argumenttyper på kommandolinjen

I årenes løb har jeg skrevet flere Java-værktøjer, der tager kommandolinjeargumenter for at kontrollere deres adfærd. Tidligt fandt jeg det irriterende at manuelt oprette og vedligeholde koden til behandling af de forskellige muligheder. Dette førte til udviklingen af ​​en prototype-klasse for at lette denne opgave, men denne klasse havde ganske vist sine begrænsninger, da antallet af mulige forskellige varianter for kommandolinjeargumenter ved nøje inspektion viste sig at være signifikant. Til sidst besluttede jeg at udvikle en generel løsning på dette problem.

Da jeg udviklede denne løsning, måtte jeg løse to hovedproblemer:

  1. Identificer alle sorter, hvor kommandolinjemuligheder kan forekomme
  2. Find en enkel måde at give brugerne mulighed for at udtrykke disse sorter, når de bruger den klasse, der endnu ikke skal udvikles

Analyse af problem 1 førte til følgende observationer:

  • Kommandolinjemuligheder i modsætning til kommandolinjedataargumenter - start med et præfiks, der entydigt identificerer dem. Præfikseksempler inkluderer en bindestreg (-) på Unix-platforme for muligheder som -en eller en skråstreg (/) på Windows-platforme.
  • Valgmuligheder kan enten være enkle afbrydere (dvs. -en kan være til stede eller ej) eller tage en værdi. Et eksempel er:

    java MyTool -a -b logfile.inp 
  • Valgmuligheder, der tager en værdi, kan have forskellige skillelinjer mellem den aktuelle optionstast og værdien. Sådanne separatorer kan være et tomt rum, et kolon (:) eller et ligetegn (=):

    java MyTool -a -b logfile.inp java MyTool -a -b: logfile.inp java MyTool -a -b = logfile.inp 
  • Indstillinger, der tager en værdi, kan tilføje endnu et niveau af kompleksitet. Overvej den måde, hvorpå Java understøtter definitionen af ​​miljøegenskaber som et eksempel:

    java -Djava.library.path = / usr / lib ... 
  • Så ud over den aktuelle valgmulighedstast (D), separatoren (=) og optionens faktiske værdi (/ usr / lib), en yderligere parameter (java.library.path) kan tage et vilkårligt antal værdier (i ovenstående eksempel kan der angives adskillige miljøegenskaber ved hjælp af denne syntaks). I denne artikel kaldes denne parameter "detalje".
  • Valgmuligheder har også en mangfoldighedsegenskab: de kan være påkrævet eller valgfri, og antallet af gange, de er tilladt, kan også variere (såsom nøjagtigt en gang, en gang eller mere eller andre muligheder).
  • Dataargumenter er alle kommandolinjeargumenter, der ikke starter med et præfiks. Her kan det acceptable antal af sådanne dataargumenter variere mellem et minimum og et maksimalt antal (som ikke nødvendigvis er det samme). Derudover kræver en applikation typisk, at disse dataargumenter skal være sidst på kommandolinjen, men det behøver ikke altid være tilfældet. For eksempel:

    java MyTool -a -b = logfile.inp data1 data2 data3 // Alle data i slutningen 

    eller

    java MyTool -a data1 data2 -b = logfile.inp data3 // Kan være acceptabelt for et program 
  • Mere komplekse applikationer kan understøtte mere end et sæt muligheder:

    java MyTool -a -b datafile.inp java MyTool -k [-verbose] foo bar duh java MyTool -check -verify logfile.out 
  • Endelig kan en applikation vælge at ignorere ukendte indstillinger eller måske betragte sådanne indstillinger som en fejl.

Så ved at udtænke en måde, der giver brugerne mulighed for at udtrykke alle disse sorter, kom jeg med følgende generelle formular, der bruges som grundlag for denne artikel:

[[]] 

Denne formular skal kombineres med flerhedens egenskab som beskrevet ovenfor.

Inden for begrænsningerne af den generelle form for en mulighed beskrevet ovenfor, er Muligheder klasse beskrevet i denne artikel er designet til at være den generelle løsning til ethvert behov for kommandoliniebehandling, som en Java-applikation måtte have.

Hjælperklasserne

Det Muligheder klasse, som er kerneklassen for løsningen beskrevet i denne artikel, kommer med to hjælperklasser:

  1. OptionData: Denne klasse indeholder al information til en bestemt mulighed
  2. OptionSet: Denne klasse har et sæt muligheder. Muligheder selv kan indeholde et vilkårligt antal af sådanne sæt

Før der beskrives detaljerne i disse klasser, andre vigtige begreber i Muligheder klasse skal introduceres.

Typesafe enums

Præfikset, separatoren og multiplicity-egenskaben er blevet fanget af enums, en funktion leveret for første gang af Java 5:

public enum Prefix {DASH ('-'), SLASH ('/'); privat char c; privat præfiks (char c) {this.c = c; } char getName () {return c; }} offentlig enum Separator {COLON (':'), EQUALS ('='), BLANK (''), INGEN ('D'); privat char c; privat separator (char c) {this.c = c; } char getName () {return c; }} offentlig enum-mangfoldighed {ONCE, ONCE_OR_MORE, ZERO_OR_ONE, ZERO_OR_MORE; } 

Brug af enums har nogle fordele: øget typesikkerhed og stram, ubesværet kontrol over sæt tilladte værdier. Enums kan også bekvemt bruges med generiserede samlinger.

Bemærk, at Præfiks og Separator enums har deres egne konstruktører, der giver mulighed for definition af en faktisk Karakter repræsenterer denne enum-forekomst (versus navn bruges til at henvise til den bestemte enum-forekomst). Disse tegn kan hentes ved hjælp af disse enums ' getName () metoder, og tegnene bruges til java.util.regex pakke mønster syntaks. Denne pakke bruges til at udføre nogle af syntakscheckene i Muligheder klasse, hvis detaljer følger.

Det Mangfoldighed enum understøtter i øjeblikket fire forskellige værdier:

  1. ENKELT GANG: Indstillingen skal forekomme nøjagtigt en gang
  2. ONCE_OR_MORE: Indstillingen skal forekomme mindst én gang
  3. ZERO_OR_ONCE: Indstillingen kan enten være fraværende eller være til stede nøjagtigt en gang
  4. ZERO_OR_MORE: Indstillingen kan enten være fraværende eller være til stede et vilkårligt antal gange

Flere definitioner kan let tilføjes, hvis behovet opstår.

OptionData-klassen

Det OptionData klasse er dybest set en datacontainer: for det første for de data, der beskriver selve indstillingen, og for det andet for de faktiske data, der findes på kommandolinjen for denne indstilling. Dette design afspejles allerede i konstruktøren:

OptionData (indstillinger. Præfiks, strengnøgle, boolsk detalje, indstillinger. Separator, separator, boolsk værdi, valgmuligheder. Mangfoldighed) 

Nøglen bruges som den unikke id for denne indstilling. Bemærk, at disse argumenter direkte afspejler de fund, der er beskrevet tidligere: en fuld beskrivelse af valgmuligheder skal mindst have et præfiks, en nøgle og mangfoldighed. Indstillinger, der tager en værdi, har også en separator og accepterer muligvis detaljer. Bemærk også, at denne konstruktør har pakkeadgang, så applikationer kan ikke bruge den direkte. Klasse OptionSet's addOption () metode tilføjer mulighederne. Dette designprincip har den fordel, at vi har meget bedre kontrol med de faktiske mulige kombinationer af argumenter, der bruges til at skabe OptionData tilfælde. For eksempel, hvis denne konstruktør var offentlig, kunne du oprette en instans med detaljer indstillet til rigtigt og værdi indstillet til falsk, hvilket naturligvis er vrøvl. I stedet for at have udførlige kontroller i selve konstruktøren besluttede jeg at levere et kontrolleret sæt af addOption () metoder.

Konstruktøren opretter også en forekomst af java.util.regex.Mønster, som bruges til denne indstillings mønstermatchningsproces. Et eksempel ville være mønsteret for en indstilling, der tager en værdi, ingen detaljer og en ikke-blank separator:

mønster = java.util.regex.Pattern.compile (prefix.getName () + nøgle + separator.getName () + "(. +) $"); 

Det OptionData klasse, som allerede nævnt, har også resultaterne af kontrollerne udført af Muligheder klasse. Det giver følgende offentlige metoder til at få adgang til disse resultater:

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

Den første metode, getResultCount (), returnerer antallet af gange, en indstilling blev fundet. Denne metodedesign hænger direkte sammen med den mangfoldighed, der er defineret for optionen. For indstillinger, der tager en værdi, kan denne værdi hentes ved hjælp af getResultValue (int-indeks) metode, hvor indekset kan variere mellem 0 og getResultCount () - 1. For værdimuligheder, der også accepterer detaljer, kan disse fås på samme måde ved hjælp af getResultDetail (int-indeks) metode.

OptionSet-klassen

Det OptionSet klasse er dybest set en container til et sæt OptionData forekomster og også de dataargumenter, der findes på kommandolinjen.

Konstruktøren har formen:

OptionSet (indstillinger. Præfiks, indstillinger. Multiplikationsstandard mangfoldighed, strengindstillingsnavn, int mindata, int maxdata) 

Igen har denne konstruktør pakkeadgang. Optionssæt kan kun oprettes via Muligheder klassen er anderledes addSet () metoder. Standardmultiplikiteten for de her specificerede indstillinger kan tilsidesættes, når du tilføjer en indstilling til sættet. Sætnavnet, der er angivet her, er en unik identifikator, der bruges til at henvise til sættet. minData og maxData er det mindste og maksimale antal acceptable dataargumenter for dette sæt.

Den offentlige API til OptionSet indeholder følgende metoder:

Generelle adgangsmetoder:

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

Metoder til at tilføje muligheder:

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) Option (Strengnøgle, boolske detaljer, separatorseparator, multiplicitetsmultiplikitet) 

Metoder til at få adgang til kontrolresultatdata:

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

Bemærk, at metoderne til at tilføje indstillinger, der tager en Separator argument oprette en OptionData eksempel acceptere en værdi. Det addOption () metoder returnerer selve den indstillede forekomst, som tillader indkaldelseskæde:

Valgmuligheder = nye indstillinger (args); options.addSet ("MySet"). addOption ("a"). addOption ("b"); 

Når kontrollerne er udført, er deres resultater tilgængelige via de resterende metoder. getOptionData () returnerer en liste over alle OptionData tilfælde, mens getOption () giver direkte adgang til en bestemt mulighed. isSet (strengnøgle) er en bekvemhedsmetode, der kontrollerer, om en indstilling blev fundet mindst én gang på kommandolinjen. getData () giver adgang til de fundne dataargumenter, mens getUmmatched () viser alle indstillinger, der findes på kommandolinjen, hvor der ikke findes nogen matchende OptionData forekomster blev fundet.

Valgklassen

Muligheder er kerneklassen, som applikationer vil interagere med. Det giver flere konstruktører, som alle tager kommandolinjens argumentstrengarray, som hoved () metode giver som det første argument:

Valgmuligheder (String args []) Valgmuligheder (String args [], int data) Valgmuligheder (String args [], int defMinData, int defMaxData) Options (String args [], Multiplicity defaultMultiplicity) Options (String args [], Multiplicity defaultMultiplicity, int data) Valgmuligheder (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) Valgmuligheder (String args [], præfiks præfiks, Multiplicity defaultMultiplicity) Options (String args [], Prefix prefix, Multiplicity defaultMultiplicity, int data) Options (String args [], Prefix prefix, Multiplicity defaultMultiplicity, int defMinData, int defMaxData) 

Den første konstruktør på denne liste er den enkleste ved at bruge alle standardværdierne, mens den sidste er den mest generiske.

Tabel 1: Argumenter for Options () -konstruktører og deres betydning

Værdi Beskrivelse Standard
præfiksDette konstruktørargument er det eneste sted, hvor et præfiks kan specificeres. Denne værdi overføres til ethvert indstillingssæt og enhver indstilling, der oprettes efterfølgende. Tanken bag denne tilgang er, at det inden for en given applikation viser sig usandsynligt, at der skal bruges forskellige præfikser.Præfiks DASH
standardMultiplicityDenne standardmultiplikitet overføres til hvert indstillingssæt og bruges som standard for indstillinger, der føjes til et sæt uden at angive en flerhed. Selvfølgelig kan denne mangfoldighed tilsidesættes for hver tilføjet valgmulighed.Multiplicity.ONCE
defMinDatadefMinData er standard minimum antal understøttede data argumenter, der sendes til hvert indstillingssæt, men det kan naturligvis tilsidesættes, når der tilføjes et sæt.0
defMaxDatadefMaxData er det maksimale standardantal understøttede dataargumenter, der sendes til hvert indstillingssæt, men det kan naturligvis tilsidesættes, når der tilføjes et sæt.0
$config[zx-auto] not found$config[zx-overlay] not found