2__09.rtfd -- /usr/axel/vorl/java97/skript/skript

Werbung
70
Copyright 1996-1997 by Axel T. Schreiner. All Rights Reserved.
Ein Framework für die Kommandozeile
Wie in Wc bereits einmal implementiert, hat sich in UNIX hat ein gewisser Standard für die
Gestaltung von Kommandozeilen eingebürgert:
Optionen gehen Argumenten voraus und beginnen mit einem Minuszeichen, dem
Flaggen folgen.
Flaggen sind einzelne Buchstaben, die beliebig in Optionen zusammengefaßt werden
können.
Zu einer Flagge kann ein Wert angegeben sein, der der Flagge als Rest der Option oder
als nächstes Argument folgt.
Ein einzelnes Minuszeichen beendet die Optionen und gilt als Argument —
normalerweise als Verweis auf die Standard-Eingabe.
Eine Option aus zwei Minuszeichen beendet die Optionen, gilt aber nicht als Argument.
Da sich sehr viele Kommandos an diesen Standard halten, bietet sich an, dessen
Implementierung in einer Framework-Klasse Main zu verkapseln: Main erhält die Argumente
von einem Klienten-Objekt und schickt Nachrichten über decodierte Flaggen und Argumente
zurück an den Klienten, der deshalb das Interface CommandLine implementieren muß.
Damit nicht alle Methoden aus diesem Interface explizit implementiert werden müssen, gibt
es eine Adapterklasse MainClient , die CommandLine implementiert, und von der ein Klient
abgeleitet werden kann, der nur einige Methoden ersetzt.
erlaubt keine Flaggen und interpretiert die Argumente als Dateinamen, aus denen
ein fortlaufender InputStream konstruiert wird. Dies ist für manche Filterprogramme recht
praktisch.
MainClient
In diesem Abschnitt wird die Implementierung des Frameworks besprochen sowie ein
triviales Beispiel main/Cmd . Realistischere Anwendungen sind eine Implementierung des
UNIX-Kommandos cat
und eine einfache Version des UNIX-Kommandos ls zur Anzeige
von Katalogen.
71
Architektur
Main(
)
run(args)
Optionen
CommandLine
flag(ch,main)
arg(fnm,main)
Argumente
arg(main)
exit(n,main)
arg()
nächstes Wort
error(...)
fatal(...)
CommandLine ist ein interface, damit es in beliebigen Klassen implementiert werden kann
(Delegate-Prinzip).
Da fatal() beim Klienten exit() aufruft, darf von dort fatal() nicht mehr aufgerufen
werden.
72
Das Interface — lib/main/CommandLine
CommandLine definiert die Methoden, die ein Klient des Frameworks implementieren muß,
denn sie werden in verschiedenen Situationen aufgerufen.
{lib/main/CommandLine.java}
package lib.main;
import java.io.*;
/** mandatory methods for client programs of Main */
public interface CommandLine {
/** called for each flag argument
* @return false to break, true to continue option loop
*/
boolean flag (char ch, Main main) throws IOException;
/** called if there is no argument except, possibly, flags
* @return code for exit()
*/
int arg (Main main) throws IOException;
/** called for each argument
* @return code for exit(); non-0 aborts argument processing
*/
int arg (String arg, Main main) throws IOException;
/** called after all arguments or as soon as arg() returns nonzero
* @return result for main.run()
*/
int exit (int code, Main main) throws IOException;
}
{}
interface kann nicht von einer Klasse abstammen und erlaubt keine implements-Klausel,
aber
man kann interface-Hierarchien mit extends bilden und dabei mehrfach vererben. Identische
Methoden dürfen aus verschiedenen interface-Deklarationen zusammenkommen.
Man muß sich Zugriffsschutz und Ausnahmen hier offenbar sehr genau überlegen:
Das interface selbst kann nur public erklärt werden. Es ist abstract.
Die Methoden können nicht geschützt werden und sie übernehmen ein public-Attribut
von interface.
Die throws-Klauseln können (wie immer) später nicht erweitert werden.
73
Die Adapterklasse — lib/main/MainClient
ist eine primitive Implementierung für CommandLine: Flaggen sind nicht erlaubt;
Argumente werden als Dateinamen interpretiert und als durchgehender InputStream über in
zur Verfügung gestellt — das hat den Nebeneffekt, daß geprüft wird, ob Lesezugriff auf alle
Argumente möglich ist.
MainClient
{lib/main/MainClient.java}
package lib.main;
import java.io.*;
/** A possible base class for client programs of Main */
public class MainClient implements CommandLine {
public boolean flag (char ch, Main m) throws IOException {
throw new IllegalArgumentException("flag -"+ch+" not permitted");
}
/** concatenated from each argument, or System.in */
protected InputStream in;
/** sets in to System.in */
public int arg (Main m) throws IOException {
in = System.in;
return 0;
}
/** adds System.in or Stream to arg to in-sequence */
public int arg (String arg, Main m) throws IOException {
if (arg == null)
// cannot happen
throw new FileNotFoundException("no filename");
InputStream next;
if (arg.length() == 0 || arg.equals("-"))
next = new FilterInputStream(System.in) {
public void close () throws IOException { }
};
else
next = new FileInputStream(arg);
in = in == null ? next : new SequenceInputStream(in, next);
return 0;
}
/** returns code */
public int exit (int code, Main m) throws IOException {
return code;
}
}
{}
Mit einem SequenceInputStream kann man eine Folge von InputStream-Objekten
nacheinander in einem einzigen InputStream verwenden. Die Lösung hat zwei Nachteile:
Alle
Dateiverbindungen sind letztlich gleichzeitig offen und man verliert die Information über die
Dateinamen, die zu den einzelnen Verbindungen geführt haben — Fehlermeldungen sind
später weniger spezifisch.
Ein Nullzeiger sollte nicht vorkommen. Ein leerer Dateiname sollte prinzipiell nicht
vorkommen, aber in einem Framework kann man auch dies zentral bearbeiten. Der
SequenceInputStream darf System.in nicht trennen, sonst kann man nicht mehrfach zugreifen.
74
Die Framework-Klasse — lib/main/Main
Main
erzeugt Nachrichten für Optionen und Argumente einer UNIX-Kommandozeile.
{lib/main/Main.java}
package lib.main;
import java.io.*;
/** A framework to deal with a UNIX-style command line */
public class Main {
/** receives method calls for decoded arguments */
protected CommandLine client;
/** client’s class’ name for messages */
public String name;
/** constructor remembers client */
public Main (CommandLine client) {
this.client = client;
name = client.getClass().getName();
}
{}
der Konstruktion eines Main-Objekts wird ein Klienten-Objekt angegeben,
Bei
das CommandLine
implementieren muß, damit ihm alle Nachrichten über die Optionen und Argumente gesandt
werden können.
{lib/main/Main.java}
/** argument list to be decoded */
protected String args [];
/** current element in args[], beyond charAt() in args[a] */
protected int a, c;
/** main loop, calls CommandLine methods */
public int run (String _args []) throws IOException {
int result; a = 0; c = 1;
block: {
// für break
if ((args = _args) != null) {
options: for (; a < args.length && args[a] != null
&& args[a].startsWith("-"); ++ a)
if (args[a].equals("-")) break options;
else if (args[a].equals("--")) { ++ a; break options; }
else
for (c = 2; c <= args[a].length(); ++ c)
if (! client.flag(args[a].charAt(c-1), this)) break options;
if (a < args.length) {
do {
// arguments loop
if (args[a] != null) c = args[a].length(); // arg() must advance
if ((result = client.arg(args[a], this)) != 0) break block;
} while (++a < args.length);
break block;
}
}
result = client.arg(this);
// no argument
}
result = client.exit(result, this);
System.out.flush();
if (System.out.checkError()) throw new IOException("output error");
return result;
}
{}
75
implementiert vor allem die Methode run(), mit der eine Kommandozeile als Vektor von
Strings analysiert wird.
Main
bezeichnet das aktuelle Argument, c die Position nach der aktuellen Flagge. Der Klient kann
mit der folgenden Methode arg() zu einer Flagge einen Wert oder zu einem Argument das
nächste abholen:
a
{lib/main/Main.java}
/** option value specified after a flag, or next argument
* @return next value on each call
* @exception ArrayIndexOutOfBoundsException if there is none
* @exception NullPointerException if there is none
*/
public String arg () {
String result =
args[a] == null || c >= args[a].length() ?
args[++a] : args[a].substring(c);
if (args[a] != null)
c = args[a].length();
// so that arg() or run() must advance
return result;
}
{}
Bei jedem Aufruf wird der nächste Wert geliefert. Da Java sowohl Vektor- als auch
String-Indizes kontrolliert, liefert arg() eine Exception, wenn unerlaubt angefragt wird.
exit() bietet dem Klienten
wie atexit() in ANSI-C.
die Möglichkeit, abschließende Arbeiten durchzuführen — ähnlich
mit Marke kann allgemein dazu verwendet werden, eine Anweisung zu verlassen —
nicht nur eine Schleife oder switch. Hier wird praktisch ein forward goto implementiert.
break
76
{lib/main/Main.java}
/** standard error messages */
public void error (String msg) {
System.err.println(name +": "+ msg);
}
public void error (Exception e) {
error(e == null ? "exception" : e.toString());
}
/** standard fatal error messages, call client.exit() and quit */
public void fatal (String msg) {
if (msg == null) error("fatal error");
else if (msg.length() > 0) error(msg);
try {
System.exit(client.exit(1, this));
} catch (IOException e) {
error(e);
System.exit(1);
}
}
public void fatal (Exception e) {
error(e);
fatal("");
}
public void fatal () {
fatal((String)null);
}
}
{}
Damit Fehlermeldungen zentral ein einheitliches Format bekommen, definiert Main dafür
einige Methoden, die insbesondere auch zur Analyse einer Exception verwendet werden
können. Auf diese Weise kann exit() auch bei einem harten Fehler noch zum Zug kommen.
Java erlaubt Overloading, also Auswahl in Abhängigkeit von Name und Parameter-Typ, für
Methoden — allerdings gilt der Typ bei der Übersetzung. Wie man bei fatal() sieht, kann
eine explizite Umwandlung hier entscheidend sein.
77
Ein Testprogramm — main/Cmd
Cmd akzeptiert die Flagge f und die Flagge v mit einem Wert. Argumente müssen Dateien
sein, die gelesen werden können.
Cmd ist ein einfacher Test für CommandLine.
{programs/main/Cmd.java}
import lib.main.*;
import java.io.*;
/** A class for a trivial command line program */
public class Cmd {
public static void main (String args []) {
Main main = new Main(new MainClient() {
public boolean flag (char ch, Main main) throws IOException {
switch (ch) {
case ’f’:
System.out.println("-"+ch); return true;
case ’v’:
System.out.println("-v "+main.arg()); return true;
default:
return super.flag(ch, main);
}
}
});
try {
System.exit(main.run(args));
} catch (ArrayIndexOutOfBoundsException e) {
main.error("value for option missing");
} catch (Exception e) {
main.error(e);
}
System.err.println("usage: "+main.name+" [-f] [-v val] file...");
main.fatal();
}
}
{}
Cmd erzeugt ein Objekt einer anonymen Unterklasse von MainClient. Beide Klassen
definieren keine eigenen Konstruktoren, erben also den parameterlosen Konstruktor von
Object .
ist public, damit der Name der Klientenklasse leicht in Fehlermeldungen
verwendet werden kann. Hier ist er aber anonym.
main.name
Die Unterklasse ersetzt nur flag() und verweist dort alle unbekannten Flaggen an die
Methode flag() in MainClient, wo die Fehlerbehandlung ausgelöst wird.
Herunterladen