Memproses argumen baris perintah di Java: Kes ditutup

Banyak aplikasi Java yang bermula dari baris perintah mengambil argumen untuk mengawal tingkah laku mereka. Argumen ini tersedia dalam argumen larik rentetan yang dimasukkan ke dalam main()kaedah statik aplikasi . Biasanya, terdapat dua jenis argumen: pilihan (atau suis) dan argumen data sebenar. Aplikasi Java mesti memproses argumen ini dan melakukan dua tugas asas:

  1. Periksa sama ada sintaks yang digunakan adalah sah dan disokong
  2. Dapatkan data sebenar yang diperlukan agar aplikasi menjalankan operasinya

Selalunya, kod yang melaksanakan tugas-tugas ini dibuat khusus untuk setiap aplikasi dan dengan itu memerlukan usaha yang besar untuk membuat dan memelihara, terutama jika keperluan melampaui kes sederhana dengan hanya satu atau dua pilihan. The Optionskelas yang dinyatakan dalam artikel ini melaksanakan pendekatan generik dengan mudah mengendalikan situasi yang paling kompleks. Kelas ini membolehkan definisi ringkas mengenai pilihan dan argumen data yang diperlukan, dan memberikan pemeriksaan sintaksis yang menyeluruh dan akses mudah ke hasil pemeriksaan ini. Ciri-ciri Java 5 baru seperti generik dan enjin yang juga digunakan untuk projek ini

Jenis argumen baris perintah

Selama bertahun-tahun, saya telah menulis beberapa alat Java yang menggunakan argumen baris perintah untuk mengawal tingkah laku mereka. Pada awalnya, saya menjengkelkan untuk membuat dan mengekalkan kod secara manual untuk memproses pelbagai pilihan. Ini menyebabkan pengembangan kelas prototaip untuk mempermudah tugas ini, tetapi kelas tersebut diakui mempunyai batasannya kerana, setelah diperiksa secara dekat, jumlah kemungkinan varieti yang berlainan untuk argumen baris perintah ternyata signifikan. Akhirnya, saya memutuskan untuk mengembangkan penyelesaian umum untuk masalah ini.

Dalam mengembangkan penyelesaian ini, saya harus menyelesaikan dua masalah utama:

  1. Kenal pasti semua jenis di mana pilihan baris perintah boleh berlaku
  2. Cari cara mudah untuk membolehkan pengguna menyatakan jenis ini semasa menggunakan kelas yang belum dikembangkan

Analisis Masalah 1 membawa kepada pemerhatian berikut:

  • Pilihan baris perintah yang bertentangan dengan argumen data baris perintah — mulakan dengan awalan yang mengenalinya secara unik. Contoh awalan termasuk tanda hubung ( -) pada platform Unix untuk pilihan seperti -aatau garis miring ( /) pada platform Windows.
  • Pilihan boleh menjadi suis sederhana (iaitu, -aboleh ada atau tidak) atau mengambil nilai. Contohnya ialah:

    java MyTool -a -b logfile.inp 
  • Pilihan yang mengambil nilai boleh mempunyai pemisah yang berbeza antara kunci pilihan sebenar dan nilai. Pemisah seperti itu boleh menjadi ruang kosong, titik dua ( :), atau tanda sama dengan ( =):

    java MyTool -a -b logfile.inp java MyTool -a -b: logfile.inp java MyTool -a -b = logfile.inp 
  • Pilihan yang mengambil nilai dapat menambahkan satu lagi tahap kerumitan. Pertimbangkan cara Java menyokong definisi sifat persekitaran sebagai contoh:

    java -Djava.library.path = / usr / lib ... 
  • Jadi, di luar kunci pilihan sebenar ( D), pemisah ( =), dan nilai sebenar pilihan ( /usr/lib), parameter tambahan ( java.library.path) dapat mengambil sejumlah nilai (dalam contoh di atas, banyak sifat persekitaran dapat ditentukan menggunakan sintaks ini ). Dalam artikel ini, parameter ini disebut "detail."
  • Pilihan juga mempunyai sifat banyak: mereka boleh diperlukan atau pilihan, dan berapa kali mereka dibenarkan juga berbeza-beza (seperti sekali, sekali atau lebih, atau kemungkinan lain).
  • Argumen data adalah semua argumen baris perintah yang tidak bermula dengan awalan. Di sini, bilangan argumen data yang boleh diterima boleh berbeza antara bilangan minimum dan maksimum (yang tidak semestinya sama). Selain itu, biasanya aplikasi memerlukan argumen data ini menjadi yang terakhir di baris perintah, tetapi itu tidak selalu berlaku. Sebagai contoh:

    java MyTool -a -b = logfile.inp data1 data2 data3 // Semua data pada akhir 

    atau

    java MyTool -a data1 data2 -b = logfile.inp data3 // Mungkin diterima oleh aplikasi 
  • Aplikasi yang lebih kompleks dapat menyokong lebih dari satu set pilihan:

    java MyTool -a -b datafile.inp java MyTool -k [-verbose] foo bar duh java MyTool -check -verify logfile.out 
  • Akhirnya, aplikasi mungkin memilih untuk mengabaikan pilihan yang tidak diketahui atau menganggap pilihan tersebut sebagai kesalahan.

Oleh itu, dalam merancang cara untuk membolehkan pengguna menyatakan semua jenis ini, saya membuat borang pilihan umum berikut, yang dijadikan asas untuk artikel ini:

[[]] 

Borang ini mesti digabungkan dengan sifat darab seperti yang dijelaskan di atas.

Dalam batasan bentuk umum pilihan yang dijelaskan di atas, Optionskelas yang dijelaskan dalam artikel ini dirancang untuk menjadi penyelesaian umum untuk setiap keperluan pemprosesan baris perintah yang mungkin dimiliki aplikasi Java.

Kelas penolong

The Optionskelas, iaitu kelas teras untuk penyelesaian yang diterangkan dalam artikel ini, datang dengan dua kelas penolong:

  1. OptionData: Kelas ini menyimpan semua maklumat untuk satu pilihan tertentu
  2. OptionSet: Kelas ini mempunyai sekumpulan pilihan. Optionsdengan sendirinya dapat menyimpan sebilangan besar set tersebut

Sebelum menerangkan perincian kelas ini, konsep penting lain dari Optionskelas mesti diperkenalkan.

Enum Jenis selamat

Awalan, pemisah, dan sifat darab telah ditangkap oleh enum, satu ciri yang disediakan untuk pertama kalinya oleh Java 5:

Awalan awalan enum {DASH ('-'), SLASH ('/'); char peribadi c; Awalan peribadi (char c) {this.c = c; } char getName () {kembali c; }} pemisah enum awam {COLON (':'), EQUALS ('='), BLANK (''), TIADA ('D'); char peribadi c; Pemisah peribadi (char c) {this.c = c; } char getName () {kembali c; }} bilangan awam yang banyak {ONCE, ONCE_OR_MORE, ZERO_OR_ONE, ZERO_OR_MORE; }

Menggunakan enum mempunyai beberapa kelebihan: keselamatan jenis meningkat dan kawalan ketat, mudah terhadap set nilai yang dibenarkan. Enum juga boleh digunakan dengan koleksi generik.

Perhatikan bahawa Prefixdan Separatorenum mempunyai konstruktor mereka sendiri, yang memungkinkan untuk menentukan definisi watak sebenar yang mewakili contoh enum ini (berbanding nama yang digunakan untuk merujuk kepada contoh enum tertentu). Watak-watak ini dapat diambil menggunakan getName()kaedah enum ini , dan watak-watak tersebut digunakan untuk java.util.regexsintaks corak paket. Pakej ini digunakan untuk melakukan beberapa pemeriksaan sintaks di Optionskelas, perinciannya akan menyusul.

The Multiplicityenum kini menyokong empat nilai yang berbeza:

  1. ONCE: Pilihan mesti berlaku sekali sahaja
  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 adalah jumlah maksimum argumen data yang disokong yang disalurkan ke setiap set pilihan, tetapi tentu saja dapat diganti ketika menambahkan satu set. 0