עיבוד ארגומנטים בשורת הפקודה ב- Java: התיק נסגר

יישומי Java רבים שהתחילו משורת הפקודה דורשים טיעונים כדי לשלוט בהתנהגותם. ארגומנטים אלה זמינים בארגומנט מערך המיתרים המועבר main()לשיטה הסטטית של היישום . בדרך כלל, ישנם שני סוגים של ארגומנטים: אפשרויות (או מתגים) וארגומנטים של נתונים בפועל. יישום Java חייב לעבד את הטיעונים הללו ולבצע שתי משימות בסיסיות:

  1. בדוק אם התחביר בו נעשה שימוש תקף ונתמך
  2. אחזר את הנתונים הנדרשים בפועל ליישום לביצוע פעולותיו

לעתים קרובות, הקוד שמבצע משימות אלה מותאם אישית לכל יישום ובכך דורש מאמץ משמעותי הן ליצירה והן לתחזוקה, במיוחד אם הדרישות חורגות ממקרים פשוטים עם אפשרות אחת או שתיים בלבד. Optionsהמעמד מתואר מיישם את המאמר הזה בגישה הגנרית בקלות להתמודד עם המצבים המורכבים ביותר. הכיתה מאפשרת הגדרה פשוטה של ​​האפשרויות הנדרשות וטיעוני הנתונים, ומספקת בדיקות תחביר יסודיות וגישה נוחה לתוצאות הבדיקות הללו. עבור פרויקט זה שימשו גם תכונות חדשות של Java 5 כמו גנריות ואנזים typesafe.

סוגי ארגומנטים של שורת פקודה

במהלך השנים כתבתי כמה כלי Java שלוקחים ארגומנטים של שורת פקודה כדי לשלוט בהתנהגותם. בשלב מוקדם מצאתי שזה מעצבן ליצור ולתחזק את הקוד באופן ידני לעיבוד האפשרויות השונות. זה הוביל לפיתוח מחלקת אב-טיפוס כדי להקל על משימה זו, אולם למעמד זה היו אמנם מגבלות שכן, בבדיקה מדוקדקת, מספר הזנים השונים האפשריים לטיעוני שורת הפקודה התגלה כמובהק. בסופו של דבר החלטתי לפתח פתרון כללי לבעיה זו.

בפיתוח פתרון זה נאלצתי לפתור שתי בעיות עיקריות:

  1. זהה את כל הזנים בהם אפשרויות שורת פקודה יכולות להופיע
  2. מצא דרך פשוטה לאפשר למשתמשים לבטא זנים אלה כאשר הם משתמשים בכיתה שעדיין לא פותחה

ניתוח בעיה 1 הביא לתצפיות הבאות:

  • אפשרויות שורת פקודה המנוגדות לטיעוני נתוני שורת הפקודה - התחל עם קידומת המזהה אותם באופן ייחודי. דוגמאות לקידומת כוללות מקף ( -) בפלטפורמות יוניקס לאפשרויות כמו -aאו קו נטוי ( /) בפלטפורמות Windows.
  • אפשרויות יכולות להיות מתגים פשוטים (כלומר, -aיכולים להיות נוכחים או לא) או לקחת ערך. דוגמה היא:

    java MyTool -a -b logfile.inp 
  • לאפשרויות שלוקחות ערך יכולות להיות מפרידים שונים בין מפתח האופציה בפועל לערך. מפרידים כאלה יכולים להיות חלל ריק, נקודתיים ( :) או סימן שווה ( =):

    java MyTool -a -b logfile.inp java MyTool -a -b: logfile.inp java MyTool -a -b = logfile.inp 
  • אפשרויות שלוקחות ערך יכולות להוסיף רמה נוספת של מורכבות. שקול את האופן שבו Java תומכת בהגדרת מאפייני הסביבה כדוגמה:

    java -Djava.library.path = / usr / lib ... 
  • לכן, מעבר למפתח האפשרויות בפועל ( D), המפריד ( =) והערך האמיתי של האופציה ( /usr/lib), פרמטר נוסף ( java.library.path) יכול לקבל על עצמו כל מספר ערכים (בדוגמה לעיל ניתן לציין מספר מאפייני סביבה באמצעות תחביר זה ). במאמר זה נקרא פרמטר זה "פרט".
  • לאופציות יש גם מאפיין ריבוי: הן יכולות להיות נדרשות או אופציונליות, ומספר הפעמים שהן מותרות יכול גם להשתנות (כגון בדיוק פעם, פעם אחת או יותר, או אפשרויות אחרות).
  • ארגומנטים של נתונים הם כולם ארגומנטים של שורת פקודה שלא מתחילים בקידומת. כאן, המספר המקובל של ארגומנטים של נתונים כאלה יכול להשתנות בין מינימום למספר מרבי (שאינו בהכרח זהה). בנוסף, בדרך כלל יישום דורש שארגומנטים נתונים אלה יהיו אחרונים בשורת הפקודה, אך לא תמיד זה חייב להיות המקרה. לדוגמה:

    java MyTool -a -b = logfile.inp data1 data2 data3 // כל הנתונים בסוף 

    אוֹ

    java MyTool -a data1 data2 -b = logfile.inp data3 // עשוי להיות מקובל על יישום 
  • יישומים מורכבים יותר יכולים לתמוך ביותר ממכלול אפשרויות אחד:

    java MyTool -a -b datafile.inp java MyTool -k [-verbose] foo bar duh java MyTool -check -verify logfile.out 
  • לבסוף, יישום עשוי לבחור להתעלם מכל אפשרויות לא ידועות או עשוי להחשיב אפשרויות כאלה כשגיאה.

לכן, תוך כדי הגדרת דרך לאפשר למשתמשים לבטא את כל הזנים הללו, הגעתי לטופס האפשרויות הכללי הבא, המשמש כבסיס למאמר זה:

[[]] 

יש לשלב טופס זה עם מאפיין הריבוי כמתואר לעיל.

במסגרת אילוצי הצורה הכללית של אפשרות שתוארה לעיל, Optionsהמחלקה המתוארת במאמר זה נועדה להיות הפיתרון הכללי לכל צורך בעיבוד שורת פקודה שיש ליישום Java.

שיעורי העוזר

Optionsבכיתה, המהווה את מעמד הליבה עבור הפתרון המתואר במאמר זה, מגיעה עם שתי כיתות עוזר:

  1. OptionData: מחלקה זו מכילה את כל המידע עבור אפשרות ספציפית אחת
  2. OptionSet: בכיתה זו יש קבוצה של אפשרויות. Optionsעצמה יכולה להכיל כל מספר של קבוצות כאלה

לפני שמתארים את פרטי הכיתות הללו, Optionsיש להציג מושגים חשובים אחרים בכיתה.

טיפוסי בטוחות בטוחות

הקידומת, המפריד ומאפיין הריבוי נלכדו על ידי enums, תכונה המסופקת לראשונה על ידי Java 5:

קידומת enum ציבורית {DASH ('-'), SLASH ('/'); char char פרטיות; קידומת פרטית (char c) {this.c = c; } char getName () {return c; }} מפריד enum ציבור {COLON (':'), EQUALS ('='), BLANK (''), NONE ('D'); char char פרטיות; מפריד פרטי (char c) {this.c = c; } char getName () {return c; }} ריבוי ציבור ציבורי {ONCE, ONCE_OR_MORE, ZERO_OR_ONE, ZERO_OR_MORE; }

לשימוש באומנויות יש כמה יתרונות: בטיחות מוגברת של סוג ושליטה הדוקה וללא מאמץ על מכלול הערכים המותרים. ניתן להשתמש בנוחות גם באוספים עם אוספים כללים.

שים לב כי Prefixל- Separatorenums יש בונים משלהם, המאפשרים הגדרה של דמות ממשית המייצגת מופע enum זה (לעומת השם המשמש להתייחס למופע enum המסוים). ניתן לאחזר תווים אלה getName()בשיטות האנומות הללו , והתווים משמשים java.util.regexלתחביר הדפוס של החבילה. חבילה זו משמשת לביצוע חלק מבדיקות התחביר Optionsבכיתה שפרטיהם יופיעו בהמשך.

MultiplicityEnum תומך בארבעה ערכים שונים:

  1. ONCE: האפשרות צריכה להתרחש בדיוק פעם אחת
  2. ONCE_OR_MORE: האפשרות צריכה להתרחש לפחות פעם אחת
  3. ZERO_OR_ONCE: האפשרות יכולה להיעדר או להיות נוכחת בדיוק פעם אחת
  4. ZERO_OR_MORE: האפשרות יכולה להיעדר או להיות כל מספר פעמים

ניתן להוסיף בקלות הגדרות נוספות אם יתעורר הצורך.

מחלקת OptionData

OptionDataבכיתה הוא בעצם מיכל נתונים: ראשית, על נתונים המתארים את האפשרות עצמה, ושנית, עבור הנתונים בפועל נמצא על שורת הפקודה עבור האפשרות כי. תכנון זה כבר בא לידי ביטוי בבנאי:

OptionData (אפשרויות. קידומת קידומת, מפתח מחרוזת, פרט בוליאני, אפשרויות. מפריד מפריד, ערך בוליאני, אפשרויות. ריבוי ריבוי) 

המפתח משמש כמזהה הייחודי לאופציה זו. שים לב כי טיעונים אלה משקפים ישירות את הממצאים שתוארו קודם לכן: תיאור מלא של האפשרות חייב לכלול קידומת, מפתח וריבוי. לאפשרויות שקובעות ערך יש גם מפריד ועשויות לקבל פרטים. שים לב גם כי לבנאי זה יש גישה לחבילה, כך שאפליקציות לא יכולות להשתמש בו ישירות. השיטה OptionSetשל הכיתה addOption()מוסיפה את האפשרויות. לעיקרון תכנון זה יש יתרון בכך שיש לנו שליטה טובה בהרבה על צירופי הארגומנטים האפשריים בפועל המשמשים ליצירת OptionDataמופעים. לדוגמא, אם הקבלן הזה היה ציבורי, תוכל ליצור מופע עם פירוט שהוגדר trueוערךfalse, שזה כמובן שטויות. במקום לבצע בדיקות משוכללות בבנאי עצמו, החלטתי לספק מערך addOption()שיטות מבוקר .

הקונסטרוקטור יוצר גם מופע של java.util.regex.Pattern, המשמש לתהליך התאמת תבניות של אפשרות זו. דוגמה אחת תהיה התבנית לאופציה שלוקחת ערך, ללא פרטים ומפריד שאינו ריק:

תבנית = java.util.regex.Pattern.compile (קידומת .getName () + מקש + מפריד. getName () + "(. +) $"); 

OptionDataבכיתה, כפי שכבר הוזכר, גם מחזיקה את התוצאות של הבדיקות המבוצעות על ידי Optionsהכיתה. הוא מספק את השיטות הציבוריות הבאות לגישה לתוצאות אלה:

int getResultCount () מחרוזת getResultValue (int index) מחרוזת getResultDetail (index index) 

השיטה הראשונה,, getResultCount()מחזירה את מספר הפעמים שנמצאה אפשרות. תכנון שיטה זה מתקשר ישירות עם הריבוי שהוגדר עבור האופציה. עבור אפשרויות שלוקחות ערך, ניתן לאחזר ערך זה getResultValue(int index)בשיטה, בה האינדקס יכול לנוע בין 0לבין getResultCount() - 1. עבור אפשרויות ערך המקבלות גם פרטים, ניתן לגשת באופן דומה באמצעות getResultDetail(int index)השיטה.

מחלקת OptionSet

OptionSetבכיתה הוא בעצם מיכל עבור קבוצה של OptionDataמקרים וגם הטיעונים נמצאו נתונים על שורת הפקודה.

לבנאי יש את הטופס:

OptionSet (קידומת Options.Prefix, Options.Multiplicity defaultMultiplicity, set string name, int minData, int maxData) 

שוב, לבנאי זה יש גישה לחבילה. ניתן ליצור ערכות אופציות רק בשיטות Optionsהשונות של הכיתה addSet(). ניתן לעקוף את ריבוי ברירת המחדל עבור האפשרויות המפורטות כאן בעת ​​הוספת אפשרות לסט. שם הסט שצוין כאן הוא מזהה ייחודי המשמש להתייחסות לסט. minDataו maxDataמינימאלי מספר מרבי של טיעוני נתונים מקובלים עבור קבוצה זו.

ה- API הציבורי של OptionSetמכיל את השיטות הבאות:

שיטות גישה כלליות:

מחרוזת getSetName () int getMinData () int getMaxData () 

שיטות להוספת אפשרויות:

OptionSet addOption (מחרוזת) OptionSet addOption (מפתח מחרוזת, ריבוי ריבוי) OptionSet addOption (מפתח מחרוזת, מפריד מפריד) OptionSet addOption (מפתח מחרוזת, מפריד מפריד, ריבוי ריבוי) (מפתח מחרוזת, פרטים בוליאניים, מפריד מפריד, ריבוי ריבוי) 

שיטות לגישה לנתוני בדיקת תוצאות:

java.util.ArrayList getOptionData () OptionData getOption (מפתח מחרוזת) בוליאני isSet (מפתח מחרוזת) java.util.ArrayList getData () java.util.ArrayList getUmatch () 

שים לב שהשיטות להוספת אפשרויות שלוקחות Separatorארגומנט יוצרות OptionDataמופע המקבל ערך. addOption()השיטות להחזיר את מופע הסט עצמה, אשר מאפשר שרשור קריאה:

אפשרויות אפשרויות = אפשרויות חדשות (טענות); options.addSet ("MySet"). addOption ("a"). addOption ("b");

לאחר ביצוע הבדיקות, תוצאותיהם זמינות בשיטות הנותרות. getOptionData()מחזירה רשימה של כל OptionDataהמקרים, תוך מתן getOption()גישה ישירה לאפשרות ספציפית. isSet(String key)היא שיטת נוחות שבודקת אם נמצאו אפשרויות לפחות פעם אחת בשורת הפקודה. getData()מספק גישה לטיעוני הנתונים שנמצאו, תוך getUnmatched()רשימת כל האפשרויות שנמצאו בשורת הפקודה שלא נמצאו OptionDataמופעים תואמים להן.

כיתת האופציות

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 הוא המספר המרבי המוגדר כברירת מחדל של טיעוני נתונים נתמכים המועברים לכל קבוצת אפשרויות, אך ניתן כמובן לבטל אותו בעת הוספת סט. 0