www.r-krell.de |
Webangebot für Schule und Unterricht, Software, Fotovoltaik und mehr |
Willkommen/Übersicht > Informatik > Informatik mit Java, Seite t2)
Fortsetzung Teil t): Simulation einer Turing-Maschine in/mit
Java
Seite t2): Verschiedene Speichermöglichkeiten
Auf der vorangegangenen Seite if-t-turi.htm wurden bereits Aufbau und Funktion einer Turing-Maschine erläutert. Außerdem wurde überlegt, welche Schritte zur Simulation einer Turing-Maschine in Java nötig sind. Von den vier dort angedeuteten Vorhaben soll hier zunächst das Problem der dauerhaften Speicherung einer Turingmaschine in eine(r) Datei untersucht werden. Drei verschiedene Ansätze werden mit Java-Quelltext vorgestellt und verglichen.
Projekt: Entwicklung einer Turing-Maschinen-Simulation,
hier: Persistente Speicherung auf Datenträger
** Achtung: wegen umfangreicher eingebetteter Java-Quelltexte ist diese Webseite sehr lang und braucht u.U. eine merkbaren Ladezeit **
Eine vollständige Übersicht aller "Informatik mit Java"-Seiten gibt's auf der Informatik-Hauptseite. Viele der dort aufgeführten Seiten enthalten ausführlich beschriebene Java-Programme im Quelltext. Außerdem wird auf die Seiten SWE1 und SWE2 zum Software-Engineering hingewiesen.
zum Seitenanfang / zum Seitenende
Will man eine Turingmaschine auf dem Computer simulieren, so soll sie nicht mit dem Beenden der Simulations-Umgebung (die wir hier programmieren wollen) verschwinden. Ebenso, wie man ein in einem Grafikprogramm erstelltes oder verändertes Bild speichern kann, um es in einer späteren Sitzung wieder zu öffnen, oder ebenso, wie man sinnvollerweise vor dem Schließen seines Textverarbeitungsprogramms die bearbeiteten Dokumente sicher abspeichert, so soll auch unser Simulator eine einmal erstellte Turingmaschine dauerhaft speichern können - auf in- oder externer Festplatte, SSD, USB-Speicherstick, Speicherkarte oder einem beliebigen anderen dauerhaften Speichermedium einschließlich einem freigegebenem Netzlaufwerk, NAS oder Cloud-Speicher. Wer noch ein entsprechendes Laufwerk hat, könnte sogar noch Disketten verwenden. Sofern das verwendete Medium bzw. der Speicherort vom Betriebssystem über einen Laufwerksbuchstaben und mit Ordnernamen (ggf. in einer geschachtelten Verzeichnisstruktur) angesprochen werden kann (dem so genannten Pfad - etwa C:\Eigene Dateien\Dokumente\Turingmaschinen oder H:\extra\Informatik), ist es zum Glück ganz egal, um welche Art von Medium es sich handelt und auf welchem physikalischen Prinzip die Speicherung beruht. Der Zugriff kann in Java vollkommen hardwareunabhängig programmiert werden. Deshalb wird zwar im Folgenden oft nur vom Speichern auf (Fest-)Platte geredet, gemeint sind mit "Platte" aber immer alle geeigneten Medien unterschiedlichster Bauform, von denen oben gerade einige genannt wurden. Auch in anderen Programmiersprachen geschriebene Anwendungen nutzen i.A. den Zugriff via Betriebssystem und können mit Dateien auf den unterschiedlichsten physikalischen Datenträgern arbeiten, ohne dass der Benutzer Unterschiede bemerkt.
zum Seitenanfang / zum Seitenende
Während den unterschiedlichen Speicher-Medien also keine besondere Aufmerksamkeit geschenkt werden muss, weil Java einen einheitlichen Zugriff erlaubt, muss man sich sehr wohl Gedanken darüber machen, in welcher Form die benötigten Informationen gespeichert werden sollen. Bekanntlich hat eine Turingmaschine ein Bandalphabet (also eine Menge verwendbarer Zeichen, zu denen auch ein Leerzeichen gehören muss), verfügt über verschiedene Zustände (wovon der erste als Startzustand und der letztgenannte als Endzustand gelten soll) und besitzt eine Maschinentafel, die für jede während der Arbeit der Maschine möglicherweise auftretende Kombination aus Eingabezeichen und aktuellem (Nicht-End-)Zustand einen Eintrag enthält, der das Ausgabezeichen, die Kopfbewegung und den Folgezustand festlegt. Das bedeutet natürlich, dass Bandalphabet, Zustandsmenge und Maschinentafel irgendwie gespeichert werden müssen.
Blättert man in der Literatur oder fahndet im Internet nach Turingmaschinen, so fällt auf, dass die Reihenfolge der Angaben in den Maschinentafel-Einträgen manchmal von der hier gewählten Ordnung abweicht: Einige Autor(inn)en geben erst den Folgezustand an, bevor sie (noch im aktuellen Zustand) das Ausgabezeichen aufs Band schreiben und dann den Kopf bewegen. Oder man findet Einträge in der Reihenfolge Ausgabezeichen, Folgezustand und Kopfbewegung. Die Kopfbewegung darf logischerweise nie vor dem Ausgabezeichen stehen, so dass zum Glück nur drei der sechs möglichen Reihenfolgen der drei Angaben berücksichtigt werden müssen - wenn man der Benutzerin oder dem Benutzer die Option geben will, die Reihenfolge innerhalb der Einträge einer fremden Turingmaschinen-Vorlage anzupassen, damit leichter eingetippt und verglichen werden kann. Die gewählte Reihenfolge sollte als Option mit gespeichert werden, damit auch beim späteren Öffnen die gleiche Reihenfolge angeboten wird.
Weiterhin wäre es schön, zusammen mit diesen notwendigen Daten einen Kommentar speichern zu können, der die Turingmaschine oder die zugehörige Aufgabenstellung so beschreibt, dass ein(e) Benutzer(in) beim Öffnen der gespeicherten Maschine informiert wird, was die Maschine kann bzw. macht.
Schließlich kann man wünschen, dass nicht nur die Maschine, sondern dazu auch eine bestimmte Bandinschrift abgelegt werden kann - am besten nicht nur mit dem anfänglichen Bandinhalt, sondern auch ein irgendwann erreichter Zwischenstand (eventuell sogar mehrere Zwischenstände). Manche Turingmaschinen brauchen Tausende von Schritten, um ein bestimmtes Ergebnis zu erreichen. Fehlt die Zeit in einer Sitzung, sollte man mit der Maschine auch die bisher benötigte Schrittzahl, das Band mit der bis dahin erreichten Inschrift, die aktuelle Position des Kopfes und den gerade aktuellen Zustand speichern können, um die begonnene Arbeit bzw. den begonnen Probelauf der Maschine später nahtlos fortsetzen zu können.
Es sind also einige Daten so abzuspeichern, dass daraus später wieder die Maschine und ihr Arbeitsfortschritt rekonstruiert werden können. Das kann in verschiedener Form geschehen:
Im Wesentlichen bieten sich drei unterschiedliche Konzepte an:
A. Speichern eines konkreten Java-Objekts (nach dem Bauplan der Java-Klasse Turing_zum_Speichern für eine
Turingmaschine mit den entsprechenden Datenfeldern, wobei das Objekt natürlich die aktuellen Werte der Felder bzw. Inhalte der Variablen
enthält). Dies kann im Java-Format mit Java-Befehlen geschehen. Die Programmierung ist relativ leicht und naheliegend. Nachteilig ist
allerdings, dass solche Dateien von fremden Programmen nicht oder kaum gelesen werden können. Würde die Java-Simulationsumgebung in
ferner Zukunft einmal durch eine Simulation in einer anderen Programmiersprache ersetzt werden müssen, wären vermutlich alle bisher
gespeicherten Turingmaschinen verloren. Insofern sollen nachstehend auch zwei zukunftssicherere Alternativen überlegt werden.
B. Speichern alle Daten in einer relationalen Datenbank mit verschiedenen Tabellen. Zwar gibt es auch hier unterschiedliche
Dateiformate, aber alle wichtigen Datenbank-Programme erlauben SQL-Befehle und die Dateiformate wichtiger Hersteller sind konvertierbar.
Datenbanken sind schon seit vielen Jahrzehnten nahezu unverändert im Einsatz und haben viele Programmiersprachen überdauert. Fast alle
Programmiersprachen ermöglichen außerdem den Zugriff auf Datenbanken und es gibt Clients verschiedenster Hersteller, so dass so
gespeicherte Daten auch in Zukunft noch gelesen werden können (selbst wenn Java mal unmodern werden sollte). Da viele Firmen massenweise Daten
in Datenbanken halten, wird bestimmt darauf geachtet, dass Datenbanken auch langfristig lesbar bleiben bzw. immer geeignete Zugriffs-Werkzeuge
existieren.
C. Ebenfalls zukunftssicher ist die Speicherung der Daten in reinem Textformat, weil sie notfalls auch in einem Editor oder in
jeder Textverarbeitung geöffnet werden könnten. Zahlenwerte dürften dann nicht in der (in Java und im Computer üblichen)
Binärform gespeichert werden, sondern ihre Dezimal-Darstellung müsste als Zeichenkette gesichert werden. Damit man später noch
weiß, welche Zahl oder welches Zeichen wofür steht, müssten hilfreiche Texte, Zwischenüberschriften oder Variablennamen
o.ä. mitgespeichert werden. Eine elegante Form der Speicherung in lesbarer Textform mit Kontext-Beschreibung bietet das zunehmend verbreitete
XML-Format.
Für jedes der drei Konzepte soll im Folgenden beispielhaft vorgestellt werden, wie eine programmtechnische Realisierung in Java aussehen kann.
Selbstverständlich muss eine einmal gespeicherte Turingmaschine später auch wieder geöffnet und vom datentragenden Medium in das Simulationsprogramm eingelesen werden können. Es werden also immer zwei zueinander passende Methoden speichere und öffne gebraucht, die im Folgenden entwickelt werden sollen.
zum Seitenanfang / zum Seitenende
Natürlich wäre es möglich, Bandalphabet, Zustandsmenge, Maschinentafel und weitere Daten jeweils getrennt in eigenen Dateien zu
speichern. Es bestünde allerdings das Risiko, dass etwa beim Verschieben oder Kopieren nicht alle benötigten Teildateien gemeinsam bewegt
werden. Insofern sind mehrere Dateien unpraktisch und verwirren den Benutzer, der außerdem gerne auf einen Dateinamen klickt, um die
gespeicherte Turingmaschine in der zugeordneten Simulations-Anwendung zu öffnen. Außerdem entspricht es dem Programmierprinzip der
Kapselung, zusammengehörende Daten auch in einem Objekt zu verwalten und somit in einer Datei zu speichern. Wie oben (zu Beginn des vorigen
Abschnitts b)) bereits beschrieben, kann es sogar sinnvoll sein, verschiedene Bandinschriften und Zwischenstände beim Ablauf der Maschine in
der gleichen Datei mit zu speichern. Geeignete Java-Objekte könnten damit nach folgendem Bauplan erzeugt werden:
public class Turing_zum_Speichern implements java.io.Serializable { char[] bandalphabet; // enthält alle les- und schreibbaren Schriftzeichen; bandalphabet[0] = Leerzeichen String[] zustand; // Namen aller Zustände; zustand[0] = Startzustand, zutand[maxIndex] = Endzustand char[] kopfbewegung; // genau drei Zeichen, z.B. 'L','S','R' oder '-','0','+' oder '<','=','>' Turing_Eintrag[][] maschinentafel; // 2-dim. Tabelle für bandalphabet.length x (zustand.length - 1) Einträge int reihenfolgenopt; // Option für Reihenfolge im Turing_Eintrag (möglich: eine der Konstanten aus Turing_Eintrag) String[] kommentar; // mehrzeilige Beschreibung/Erläuterung zur Turing-Maschine Turing_Band[] bandbeschriftung; // Bandinschriften mit Position, Kopfposition und Anzahl der Schritte bis zur Inschrift public Turing_zum_Speichern (char[] abc, String[] zustände, char[] richtungen, Turing_Eintrag[][] tabelle, int opt, String[] anmerkung, Turing_Band[] bänder) { bandalphabet = abc; zustand = zustände; kopfbewegung = richtungen; maschinentafel = tabelle; reihenfolgenopt = opt; kommentar = anmerkung; bandbeschriftung = bänder; } } |
wobei hier die Beschreibungen für Eintrag und Band in eigene Klassen
ausgelagert wurde:
public class Turing_Eintrag implements java.io.Serializable { final static int ZCH_KOPF_ZST = 0; // Option für die Reihenfolge Ausgabezeichen - Kopfbewegung - Folgezustand final static int ZCH_ZST_KOPF = 1; // Option für die Reihenfolge Ausgabezeichen - Folgezustand - Kopfbewegung final static int ZST_ZCH_KOPF = 2; // Option für die Reihenfolge Folgezustand - Ausgabezeichen - Kopfbewegung final static int LINKS = -1; // interne Darstellung der Kopfbewegung, unabhängig von ihrer Anzeige als kopfbewegung[0] final static int STILL = 0; // interne Darstellung der Kopfbewegung, unabhängig von ihrer Anzeige als kopfbewegung[1] final static int RECHTS = +1; // interne Darstellung der Kopfbewegung, unabhängig von ihrer Anzeige als kopfbewegung[2] char neuesZeichen; // Ausgabezeichen, das an der Kopfposition aufs Band geschrieben wird int kopfbewegung; // Bewegung des Kopfes (Werte sind nur LINKS, STILL oder RECHTS) String neuerZustand; // Folgezustand, den die Turingmaschine für den nächsten Arbeitsschritt annimmt public Turing_Eintrag (char zchn, int richtung, String zst) { neuesZeichen = zchn; kopfbewegung = richtung; neuerZustand = zst; } } |
public class Turing_Band implements java.io.Serializable { int schrittzahl; // gibt an, nach wie vielen Arbeitsschritten die Bandinschrift erreicht wurde int indexBeschriftungsstart; // Position von bandinschrift[0] auf dem Band int indexKopf; // Position des Kopfes String bandinschrift; // gespeicherte Bandinschrift String kommentar; // Anmerkung zur Bandinschrift bzw. zum erreichten Zwischenstand public Turing_Band (int schritte, int start, int kopfposition, String inschrift, String anmerkung) { schrittzahl = schritte; indexBeschriftungsstart = start; indexKopf = kopfposition; bandinschrift = inschrift; kommentar = anmerkung; } } |
Bei allen drei Klassen muss implements java.io.Serializable angegeben werden, damit später ein Objekt nach dem Bauplan von Turing_zum_Speichern auf den Datenträger geschrieben werden kann - obwohl hier wirklich nur ein einziges Objekt geschrieben bzw. gelesen werden soll.
Vielleicht fällt auf, dass in der Klasse Turing_Eintrag die kopfbewegung vom Typ int (Ganzzahl) ist. Weil verschiedene Autoren unterschiedliche Symbole für die Kopfbewegung verwenden und der Benutzer vielleicht mit verschiedenen Darstellungen experimentieren möchte, soll hier die Speicherung immer mit den Werten -1, 0 oder +1 erfolgen (zugehörige Konstanten mit den sprechenden Namen LINKS, STILL und RECHTS werden zur Verfügung gestellt). Was tatsächlich dann auf dem Bildschirm für die jeweilige Kopfbewegung angezeigt wird, kann der Benutzer jederzeit frei wählen und steht in der Buchstabenreihung kopfbewegung in der Klasse Turing_zum_Speichern. Bei einer Änderung der Anzeige-Symbole büßt die gespeicherte Maschine nicht ihre Funktionsfähigkeit ein, weil sie intern weiter mit den gleichen Zahlenwerten arbeitet.
In ähnlicher Weise hätten auch die jetzt im Eintrag gespeicherten willkürlich zu wählenden Zustandsnamen intern durch ein Nummerierung (etwa den Index der Reihung zustand aus der Klasse Turing_zum_Speichern) ersetzt werden können. Allerdings werden Zustandsbezeichnungen für eine bestimmte Turingmaschine wohl nicht so schnell geändert - ansonsten müsste hier der Benutzer von Hand die Einträge in der Maschinentafel anpassen, damit die Maschine wieder läuft.
zum Seitenanfang / zum Seitenende
Wie man ein oder mehrere Java-Objekte auf einen Datenträger schreibt bzw. später wieder davon liest, habe ich bereits auf meiner "Informatik mit Java"-Seite d) angegeben (und zwar unter 'Dateioperationen in Java' im Abschnitt 3. des Extrablatts, das dort als pdf-Datei angesehen oder herunter geladen werden kann, sowie am Ende des Quelltextes der Erweiterten Fuhrparkverwaltung in der Datei 2: Klasse KfzFuhrpark). Außerdem gibt es ein Beispiel als Hilfe auf der letzten Seite des Aufgabenblatts der 3. Klausur EF vom 19.3.2012: Man muss fürs Schreiben (speichere bzw. auf Disk) einen ObjectOutputStream über einen FileOutputStream stülpen, und dann das oder die Objekte schreiben. Weil immer nur eine Turingmaschine in eine Datei kommt, gibt es jetzt immer nur ein Objekt und Wiederholstrukturen können entfallen. Und beim Lesen (öffne bzw. vonDisk) muss man nicht so lange Objekte lesen, bis das Dateiende erreicht wird, sondern kann ebenfalls einfach ein Objekt lesen. Insofern ergeben sich folgende einfache Methoden:
import java.io.*; public class Turing_Speichertest // Demo: Speichern und Öffnen einer Turingmaschine als Java-Objekt. r-krell.de, 6/2019 { public Turing_Speichertest() // Konstruktor mit Anweisungen für einen Testlauf { Turing_zum_Speichern tm1 = beispiel_bitaddition_kurz(); zeige (tm1); speichere (tm1, "turi$$$.tma"); // schreibt auf den Datenträger Turing_zum_Speichern tm2 = öffne ("turi$$$.tma"); // liest vom Datenträger zeige (tm2); } public Turing_zum_Speichern beispiel_bitaddition_kurz() // erzeugt Beispiel-Turingmaschine (wie auf voriger Webseite abgebildet) { ... } // gibt Turingmaschine zum Addieren zweier Bits als Beispiel zurück; Quelltext folgt später am Ende von Abschnitt Ba) public void zeige (Turing_zum_Speichern tm) { ... } // zeigt Turingmaschinen-Daten in der Konsole an; Quelltext hier nicht angegeben public void speichere (Turing_zum_Speichern tm, String datname) // schreibt tm auf Datenträger { try { ObjectOutputStream datei = new ObjectOutputStream (new FileOutputStream (datname)); datei.writeObject (tm); datei.close(); } catch(IOException e) { System.out.println("** Datei-Schreibfehler "+e+" **"); } } public Turing_zum_Speichern öffne (String datname) // gibt vom Datenträger gelesene Turingmaschine als Funktionswert zurück { Turing_zum_Speichern tm = null; try { ObjectInputStream datei = new ObjectInputStream (new FileInputStream (datname)); tm = (Turing_zum_Speichern)datei.readObject(); datei.close(); } catch (Exception e) { System.out.println("** Datei-Lesefehler "+e+" **"); } return (tm); } } |
Beim Start des Testprogramms wird - wegen der Programmzeilen im Konstruktor - zunächst eine Turingmaschine als Objekt tm1 erzeugt und dabei mit einem von meiner vorigen Webseite bekannten Beispiel gefüllt, mit zeige angezeigt und dann auf dem Datenträger gespeichert. Zur Kontrolle werden die gerade gespeicherten Daten anschließend wieder vom Datenträger in das neue Objekt tm2 gelesen und erneut angezeigt. Die Methoden funktionieren fehlerlos und zwei gleiche Ausgaben belegen, dass durch Speichern und Öffnen keine Daten verloren gingen oder verändert wurden:
Bei der Ausgabe der Maschinentafel durch die hier nicht mit Quelltext dargestellte Methode zeige wurden zur Kontrolle noch die Koordinaten x (=Index des gelesenen [Eingabe-]Zeichens im bandalphabet) und y (=Index des alten bzw. aktuellen Zustands in der Reihung zustand) jedes Eintrags mit ausgegeben; diese Koordinaten sind aber nicht in jedem Eintrag zusätzlich gespeichert, sondern sind der Index bzw. die Indizes des Eintrags maschinentafel[x][y] - vgl. die oben in Aa) angegebene Klasse Turing_zum_Speichern.
zum Seitenanfang / zum Seitenende
Auf dem Datenträger werden die Informationen in der Dualdarstellung als eine Folge von Bits mit den Werten 0 oder 1 gespeichert. Öffnet man die hier erzeugte und gelesene Datei 'turi$$$.tma' mit einem Hexeditor, so werden jeweils 4 aufeinanderfolgende Bits zu einem Zeichen 0..F des Hexadezimal-(=16er-)Systems zusammengefasst; 8 Bits bzw. zwei Hexziffern bilden ein Byte und können entweder einen normalen Buchstaben repräsentieren oder sind Teil einer Zahl oder eines anderen Datentyps. Sonderzeichen wie 'ü' werden bei UTF-8 durch 2 Bytes bzw. vier Hex-Ziffern dargestellt; die getrennte Übersetzung der beiden Teil-Bytes liefert Buchstabensalat (siehe blau ausgewähltes Beispiel). Während man den abgespeicherten Text noch erahnen kann, lassen sich die gespeicherten Zahlen nicht erkennen:
Wegen der nicht ohne Weiteres erkennbaren Zuordnung der gespeicherten Bits zu benötigten Zahlen, Trennmarken und weiteren Informationen haben in einer anderen Programmiersprache geschriebene Programme - wie oben bereits erwähnt - Schwierigkeiten mit dem Lesen solcher Dateien. In der Vergangenheit gab es gelegentlich sogar innerhalb von Java Probleme, wenn der Quelltext nach einem Versionsupdate neu kompiliert wurde und die neue Java-Version andere Trennzeichen zur Serialisierung eingefügt bzw. erwartet hat. Angeblich konnte auch das Hinzufügen weiterer Methoden etwa zur Klasse Turing_zum_Speichern trotz unveränderter Datenfelder die Serialisierung und damit die Lesbarkeit alter Dat(ei)en ändern. Mit jetzt versuchsweise zur Klasse hinzugefügten Methoden trat bei mir das Problem dieses Mal glücklicherweise nicht auf, sodass das Programm offenbar durchaus weiter entwickelt werden kann, ohne dass alte Turingmaschinen unlesbar werden. Aber wirklich zukunftssicher und vor allem programmiersprachenunabhängig ist die Speicherung in diesem Format leider nicht. Obwohl sich diese Variante bequem und einleuchtend implementieren lässt, mit unverändertem Programm rasch und zuverlässig funktioniert und die erzeugten Dateien recht kompakt sind, soll daher noch nach Alternativen gesucht werden.
zum Seitenanfang / zum Seitenende
Insbesondere auf meiner zweiten Seite DB2 über Datenbanken habe ich schrittweise erläutert, dass relationale Datenbanken i.A. aus mehreren Tabellen bestehen. Um mindestens die zweite Normalform zu erreichen, müssen die Einträge innerhalb einer Tabellenzeile (=innerhalb eines Datensatzes) 'voll funktional' vom (Primär-)Schlüssel abhängen. Außerdem steht in jedem Tabellenfeld immer höchstens ein einzelnes, atomares Datum mit klarer Zuordnung zur Spaltenüberschrift. Damit über verschiedene Tabellen verteilte Informationen sicher wieder zusammengeführt werden können, muss die Referenzintegrität der Datenbank garantiert werden. Begriffe wie Primär- und ^Fremdschlüssel spielen dabei eine wichtige Rolle. Die Kunst des Datenbankentwurfs besteht darin, ein geeignetes Tabellenschema zu erstellen, das alle Informationen redundanzfrei aufnimmt und im Betrieb Anomalien vermeidet. Graphische Veranschaulichungen wie etwa ein Entity-Relationship-Diagramm können dabei helfen. Während ich für die Konstruktionsprinzipien von Datenbanken auf meine genannte Seite DB2 verweise, soll hier ein fertig konzipiertes Tabellenschema vorgestellt werden, das den Anforderungen genügt und das Abspeichern bzw. das sichere Verwahren aller Informationen erlaubt, die für die Beispiel-Turingmaschine benötigt werden und die oben im Kapitel A in einem einzigen Java-Objekt nach dem Bauplan der Klasse Turing_zum_Speichern Platz fanden:
Erstellt man für die Tabellen ein ERD, so fällt noch stärker als durch die oben fehlenden Pfeile auf, dass drei Tabellen (Entitäten) nicht mit dem Rest verbunden sind:
Sollen mehrere Bandinschriften bzw. Zwischenstände zur gleichen Turingmaschine gespeichert werden, würden in der Band-Tabelle weitere Zeilen angefügt - ähnlich wie in Java die Reihung band durch weitere Komponenten vom Typ Turing_Band vergrößert werden kann. Zum direkten Vergleich der Tabelleneinträge mit dem Javatext wird nachfolgend die Java-Methode beispiel_bitaddition_kurz() doch noch vollständig angegeben. Ihr Quelltext fehlte oben im Abschnitt Ab) in der Klasse Speicher_Test, um nicht von den Methoden speichere und öffne abzulenken. Die 'echten' Namen der Java-Attribute finden sich noch weiter oben auf dieser Seite im Abschnitt Aa) in der Klasse Turing_zum_Speichern. Sie entsprechen den Tabellennamen.
Nachtrag/Teil der Klasse Turing_Speichertest
: public Turing_zum_Speichern beispiel_bitaddition_kurz() // erzeugt Beispiel-Turingmaschine (wie auf voriger Webseite abgebildet) { char[] abc = {'_','0','1'}; String[] zustandsnamen = {"1","2","3","4","5"}; // oder {"q0", "q1", ..} oder {"Start", "A", ..} ... char[] bewrichtungen = {'<', '=', '>'}; // oder {'L','S','R'} oder {'-','0','+'} oder ... Turing_Eintrag[][] mt = new Turing_Eintrag[3][4]; // Maschinentafel mt[0][0]=new Turing_Eintrag('_',0,"1"); mt[1][0]=new Turing_Eintrag('_',1,"2"); mt[2][0]=new Turing_Eintrag('_',1,"3"); mt[0][1]=new Turing_Eintrag('_',1,"2"); mt[1][1]=new Turing_Eintrag('0',1,"5"); mt[2][1]=new Turing_Eintrag('1',1,"5"); mt[0][2]=new Turing_Eintrag('_',1,"3"); mt[1][2]=new Turing_Eintrag('1',1,"5"); mt[2][2]=new Turing_Eintrag('1',1,"4"); mt[0][3]=new Turing_Eintrag('0',1,"5"); // mt[1][3]=new Turing_Eintrag('0',0,"4"); mt[2][3]=new Turing_Eintrag('1',0,"4"); wenn grau int reihenfolge = Turing_Eintrag.ZCH_KOPF_ZST; // entspricht dem Wert 0 der Option, vgl. Klasse Turing_Eintrag String[] hinweis = {"Diese Turingmaschine addiert zwei Bits.", "Sie ist am Ende des Abschnitts b2) auf www.r-krell.de/if-t-turi.htm als zweite, kürzere Variante", "beschrieben und wird hier als willkürlich ausgesuchtes Beispiel verwendet."}; Turing_Band[] band = {new Turing_Band (0, 2, 2, "1_1", "Die Summe 1+1=10 soll die beiden Summanden ersetzen")}; return (new Turing_zum_Speichern (abc, zustandsnamen, bewrichtungen, mt, reihenfolge, hinweis, band)); } |
Aus Platzgründen wurden die Kopfbewegungen in der Mitte jeden Eintrags direkt mit ihrer Zahl (meist 1 für rechts) statt mit der
Konstanten Turing_Eintrag.RECHTS eingegeben.
Der Vergleich vom grün unterlegten Javatext und dem handschriftlichen Tabellenschema zeigt, dass alle Javadaten auch in den Tabellen der
Datenbank vorkommen. Während in der Datenbanktabelle für die Maschinentafel jeder Eintrag direkt mit dem aus den beiden Attributen
altesZeichen und alterZustand gemeinsam gebildeten Primärschlüssel identifiziert
wird, erfolgt in Java die Indizierung mt[x][y] indirekt mit den Zahlen x (=Index des gelesenen, 'alten' [Eingabe-]Zeichens im
bandalphabet) und y (=Index des alten bzw. aktuellen Zustands in der Reihung zustand, definiert in der Klasse
Turing_zum_Speichern).
zum Seitenanfang / zum Seitenende
Die Datenbank selbst wird nicht von Java verwaltet, sondern von einem Datenbank-Managementsystem wie etwa MS Access, Libre oder Open Office Base, DB2, Oracle, ... oder MySQL. Ich habe MySQL verwendet, mir die nötigen SQL-Befehle zum Erzeugen der oben gezeigten Tabellen überlegt und diese SQL-Anweisungen dann zur Kontrolle in einen Querybrowser (also noch ohne Java und ohne ein Turingmaschinen-Simulationsprogramm) von Hand eingetippt und ausprobiert. Im nachstehend gezeigten, erfolgreichen Test wurden vier Tabellen angelegt und wie in der handschriftlichen Tabellenübersicht gefüllt; nur in die Maschinentafel wurden zunächst nur die ersten drei Datensätze aufgenommen (siehe Bild des Querybrowsers, Zeile 24). Unten links wird zur Kontrolle gerade die Zustandstabelle ausgegeben; der Wahrheitswert true wird dabei als 1 dargestellt.
Auf meiner dritten Datenbankseite DB3 habe ich ein Java-Programm mit erläutertem Quelltext ausführlich vorgestellt, das die vollständige Kommunikation zwischen Java und dem MySQL-Server für das Beispiel einer Fuhrparkverwaltung durchführt. Für die Klasse DBJ_JFunktion werden dort acht verschiedene Methoden angegeben - von der Kontaktaufnahme mit dem Datenbankserver über das Erzeugen einer Datenbank, dem Einfügen von Datensätzen (Speichern) bis zum Abfragen/Auslesen (Öffnen) und Abmelden - die hier nicht alle wiederholt werden sollen. Nur der Javatext zum Übermitteln der ersten fünf SQL-Befehle (Zeilen 1 bis 6 im abgebildeten Querybrowser) soll hier exemplarisch gezeigt werden:
import java.sql.*; ... public void führe5BefehleAus() { try { Class.forName("com.mysql.jdbc.Driver"); // mysql-connector-java-x.xx.x-bin.jar muss im Classpath sein Connection con = DriverManager.getConnection ("jdbc:mysql://localhost/?user=root&password="); // Anmeldung an lokalem MySQL-Server Statement stmt = con.createStatement(); int z1 = stmt.executeUpdate("create database turing;"); int z2 = stmt.executeUpdate("use turing;"); int z3 = stmt.executeUpdate("create table bandalphabet (zeichen char(1) primary key not null, leerzeichen bool unique);"); int z4 = stmt.executeUpdate("insert into bandalphabet values ('_',true);"); int z5 = stmt.executeUpdate("insert into bandalphabet (zeichen) values ('0'),('1');"); //.. und mehr int sum = z1 +z2 + z3 +z4 + z5; System.out.println("Die SQL-Befehle haben "+z1+"+"+z2+"+"+z3+"+"+z4+"+"+z5+"="+sum+" Datenbankzeilen betroffen."); } catch (Exception ex) { System.out.println("** Fehler beim Anmelden am MySQL-Server oder beim Schreiben in die Datenbank:"+ex); } } |
Allerdings eignet sich vorstehender Javatext nur dazu, genau die Beispielturingmaschine zu speichern. Sinnvollerweise soll aber jede beliebige Turingmaschine gespeichert werden können, die sich gerade im Objekt tm der Klasse Turing_zum_Speichern befindet. Anstelle der Methode speichere in der Klasse Turing_Speichertest aus Abschnitt Ab) könnte dann dort etwa die folgende Methode stehen (wobei import java.sql.*; statt import java.io.*; benötigt wird und bei keiner Klasse mehr implements java.io.Serializable gebraucht wird):
public void speichereInDB (Turing_zum_Speichern tm, String dbname) // schreibt tm in eine Datenbank namens dbname { try { Class.forName("com.mysql.jdbc.Driver"); // mysql-connector-java-x.x.xx-bin.jar muss im Classpath sein Connection con = DriverManager.getConnection ("jdbc:mysql://localhost/?user=root&password="); // Anmeldung an lokalem MySQL-Server Statement stmt = con.createStatement(); int z1 = stmt.executeUpdate("create database "+dbname+";"); int z2 = stmt.executeUpdate("use "+dbname+";"); int z3 = stmt.executeUpdate("create table bandalphabet (zeichen char(1) primary key not null, leerzeichen bool unique);"); int z4 = stmt.executeUpdate("insert into bandalphabet values ('"+tm.bandalphabet[0]+"',true);"); String sqlBefehl = "insert into bandalphabet (zeichen) values "; for (int i=1; i<tm.bandalphabet.length; i++) { sqlBefehl = sqlBefehl + "('"+tm.bandalphabet[i]+"')"; if (i < tm.bandalphabet.length-1) {sqlBefehl = sqlBefehl + ",";} else {sqlBefehl = sqlBefehl + ";";} } int z5 = stmt.executeUpdate(sqlBefehl); //.. und viel mehr für weitere 6 Tabellen } catch (Exception ex) { System.out.println("** Fehler beim Anmelden am MySQL-Server oder beim Schreiben in die Datenbank:"+ex); } } |
Achtung: Diese Methode ist noch unvollständig. Sie schreibt nicht etwa den ganzen Inhalt von tm, sondern bisher nur das eine Attribut bandalphabet in eine Tabelle. Für die weiteren Attribute der Klasse Turing_zum_Speichern müssen noch sechs weitere Tabellen angelegt und gefüllt werden. So wie beim Bandalphabet alle Zeichen einzeln einem Text sqlBefehl mit passender SQL-Syntax hinzugefügt werden müssen, sind auch bei allen anderen Tabellen Wiederholstrukturen für die verschiedenen Datenbanktabellenzeilen nötig; bei der Maschinentafel bedarf es sogar zweier geschachtelter Schleifen mit innerer if-Abfrage, um jeden vorhandenden Eintrag in eine Zeile der Tabelle bzw. passend in runde Klammern für den insert..values-Befehl zu schreiben. Im Kapitel A wurde hingegen das ganze Objekt mit allen Attributen mit einer einzigen Java-Anweisung datei.writeObject (tm); auf den Datenträger gebracht (s.o.).
Das Öffnen bzw. das Auslesen einer Turingmaschine aus einer Datenbank ist noch ein bisschen aufwändiger, weil man die gefundenen Daten erst etwas mühsam aus dem ResultSet des Statements herauslesen (parsen) muss, um sie den richtigen Java-Variablen bzw. Komponenten zuweisen zu können.
zum Seitenanfang / zum Seitenende
Das Speichern einer Turingmaschine in eine(r) Datenbank ist möglich und recht zukunftssicher - auch wenn neben den relationalen Datenbanken
inzwischen zunehmend weniger stark strukturierte, nicht-relationale NoSQL-Datenbanken aufkommen. Wegen der starken Verbreitung von relationalen
Datenbanken und insbesondere von MySQL-Datenbanken wird es diese Speicherform bestimmt noch sehr lange geben und es gibt massenweise Programme und
Werkzeuge, die die Daten lesen und verwenden können. Das ist auch künftig zu erwarten.
Ein Javaprogramm mit Datenbank-Speicherung verlangt aber einen erhöhten Programmieraufwand, zusätzliche Datenbankkenntnisse vom
Entwickler bzw. der Entwicklerin und erfordert vor allem während jeder Ausführung der Anwendung den Zugriff auf ein laufendes
Datenbankmanagementsystem wie etwa einen MySQL-Server. Das schränkt die Nutzerfreundlichkeit der entsprechenden Turingsimulation insbesondere
im lokalen Stand-Alone-Betrieb deutlich ein bzw. macht von Fremdprogrammen und/oder einer Verbindung zum Datenbankserver abhängig. Insofern
soll im Folgenden noch ein weiterer Ansatz untersucht werden.
zum Seitenanfang / zum Seitenende
Natürlich kann man die Daten der Turingmaschine leicht als einfachen Text speichern. Das Bild zeigt die Werte der vorstehend schon mehrfach als Beispiel bemühten Turingmaschine zur Addition zweier Bits:
Eine an das CSV-Format (comma seperated values, das häufig bei der Textdarstellung von Tabellen verwendet wird) angelehnte Darstellung könnte die Zusammenhänge besser verdeutlichen, wobei hier allerdings sowohl Kommas wie Semikolons zur Trennung der von Anführungszeichen umschlossenen Inhalte eingesetzt wurden. Jetzt wurden das Bandalphabet, die drei Kopfbewegungs-Symbole und die Bandinschrift jeweils zu einer Zeichenkette zusammengefasst. Damit das Ende der Maschinentafel klar ist, werden fehlende Einträge - ob am Anfang, mittendrin oder am Ende der Tafel - durch ein Semikolon ohne vorhergehende(n) Wert(e) repräsentiert.
Nachteilig an der Version 2 ist allerdings, dass ein Mensch die vielen Zeichen, Ziffern, Zahlen und Werte nicht ohne Weiteres richtig als Zustände, Bandzeichen, Teile eines Maschinentafeleintrags oder Option erkennt und man deshalb auf das Java-Programm oder zumindest den Quelltext der Klasse Turing_zum_Speichern aus Abschnitt Aa) angewiesen ist, um Version_2.txt zu verstehen und daraus die Maschine zu rekonstruieren. Zusätzliche erklärende Hinweise oder zumindest Zwischenüberschriften, etwa wie bei der Konsolenausgabe am Ende von Abschnitt Ab), könnten helfen. Noch sicherer und übersichtlicher ist es, Anfang und Ende von jedem Wert deutlich zu markieren. Bei XML (extensible markup language = erweiterbare Auszeichnungssprache) verwendet man ähnlich wie in HTML dafür Tag-Paare, wobei jedes Tag (tag = Schildchen, Etikett) eine beliebige (aber sinnvoll beschreibende) Bezeichnung in spitzen Klammern enthält:
Im farbig unterlegten Tag-Paar mit den Symbolen für die Kopfbewegung (Zeile 8) mussten die gewünschten Zeichen '<' und '>' für links und rechts durch die HTML-übliche Umschreibung mit < und > (für das Kleiner- und das Größer-Zeichen) ersetzt werden, damit sie nicht für den Anfang und das Ende eines Tags mit der Bezeichnung = gehalten werden (zu dem dann das End-Tag <\=> fehlen würde). Allgemein muss jeder XML-Text 'wohlgeformt' sein, d.h. Inhalte müssen von Anfangs- und End-Tags umschlossen werden, das End-Tag darf nicht vor dem Anfangs-Tag stehen, und Tags müssen ordentlich geschachtelt sein - so ist etwa <a><b></a></b> verboten. In einem besonderen Abschnitt anfangs innerhalb einer XML-Datei oder in einem separaten Schema-Dokument könnte außerdem festgelegt werden, dass z.B. das Tag-Paar Zustände (im Bild in den Zeilen 5 bis 7) hintereinander beliebig viele Tag-Paare für jeweils einen Zustand enthalten darf, aber sonst nichts. Und natürlich können auch die formalen Anforderungen, d.h. alle benötigten oder zulässigen Tags und ihre Anzahl und Reihenfolge, für die komplette Turingmaschine beschrieben werden. Auf solche Formalisierungen in XML- bzw. XSD-Syntax wird hier aber verzichtet; entsprechende Festlegungen finden später im Java-Programmtext der zu entwickelnden Anwendung ihren Niederschlag.
Angemerkt sei noch, dass im gezeigten Beispiel darauf verzichtet wurde, leere Maschinentafeleinträge als <eintrag></eintrag> abzuspeichern. Für den Menschen ist es einleuchtender, in der Abteilung <alt>...</alt> zu sehen, für welche vorgefundene Zeichen-Zustands-Kombination der Eintrag gelten soll, und dann in der Abteilung <neu>...</neu> zu lesen, welches neue Zeichen, welche Kopfbewegung und welcher (Folge-)Zustand für diese Situation vorgesehen sind. Auch der Computer kann statt durch Abzählen der Einträge bei passender Programmierung aus den alt-Angaben herausfinden, wo der Eintrag in der Maschinentafel steht, sodass leere Einträge überflüssig sind. Ähnliches galt schon in bzw. bei der Datenbanktabelle für die Maschinentafel in Abschnitt Ba).
Grundsätzlich sind die Textversionen 2 und 3 beide für das Speichern und Öffnen / Wiedereinlesen einer Turingmaschine geeignet. Der Text der Version 3 ist leichter lesbar, weitgehend selbsterklärend, aber deutlich umfangreicher. Weil aber Java schon vorgefertigte Bibliotheksfunktionen zum Erzeugen und Einlesen von XML-Dateien mitbringt, ist die Programmierung für die Version 3 hoffentlich nicht aufwändiger als der Selbstbau eines Parsers für die kürzere Version 2, die allerdings ja auch noch durch Überschriften und Kommentare ergänzt werden sollte. Der XML-Text (Version 3) ist von Anfang an übersichtlicher und zukunftssicherer. Deswegen wird im Folgenden das XML-Format gewählt.
zum Seitenanfang / zum Seitenende
Da der Inhalt der Datei - egal in welchem des im vorigen Abschnitt Ca) beschriebenen Format bzw. egal in welcher Textversion - aus reinem Text
besteht, können in jedem Fall die altbekannten Java-Verfahren zum Schreiben und Lesen von Textdateien verwendet werden, auch für die
XML-Datei. Später wird dann zusätzlich die Tauglichkeit spezieller Java-Bibliotheken für den Umgang mit XML-Dateien
geprüft.
Im Folgenden werden einfach alle Zeichen, die später in der XML-Datei stehen sollen (siehe Bildschirmabdruck eines Texteditors mit der geöffneten XML-Datei etwas weiter oben) einschließlich Einrückungen und Zeilenumbrüchen \n in den BufferedWriter ausgabe geschrieben, der die Datei füllt. Für wiederkehrende Operationen - wie das Umhüllen von Inhalten mit Start- und End-Tag - wurden eigene private Methoden geschrieben (s.u.).
public void speichere1 (Turing_zum_Speichern tm, String datname) // schreibt tm per normalem Stream als XML auf Datenträger { meldung = ""; try { FileOutputStream aufDisk = new FileOutputStream (datname); BufferedWriter ausgabe = new BufferedWriter (new OutputStreamWriter (aufDisk, "UTF-8")); ausgabe.append ("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"); ausgabe.append ("<turingmaschine>\n"); ausgabe.append (" <!-- Gespeicherte Turingmaschine für Programm \"TuringMa_J\", www.r-krell.de/if-t-turi.htm -->\n"); ausgabe.append (" "+element ("bandalphabet", new String(tm.bandalphabet))+" <!-- erstes Zeichen ist das Leerzeichen -->\n"); ausgabe.append (" <zustaende> <!-- erster Zustand ist Startzustand, letzter ist Haltzustand -->\n"); ausgabe.append (" "); for (int z = 0; z < tm.zustand.length; z++) ausgabe.append (element("zustand",tm.zustand[z])+" "); ausgabe.append ("\n </zustaende>\n"); ausgabe.append (" "+element ("kopfbewegung", new String(tm.kopfbewegung))+"\n"); ausgabe.append (" <maschinentafel>\n"); for (int y = 0; y < tm.zustand.length-1; y++) { for (int x = 0; x < tm.bandalphabet.length; x++) { if (tm.maschinentafel[x][y]!=null) { ausgabe.append (" <eintrag> <alt>"+element("zeichen",""+tm.bandalphabet[x]) + element ("zustand", ""+tm.zustand[y])+"</alt>\n <neu>" + element ("zeichen", ""+tm.maschinentafel[x][y].neuesZeichen) + element ("bewegung",""+tm.maschinentafel[x][y].kopfbewegung) + element ("zustand",tm.maschinentafel[x][y].neuerZustand) + "</neu> </eintrag>\n"); } } } ausgabe.append (" </maschinentafel>\n"); ausgabe.append (" "+element("option",""+tm.reihenfolgenopt)+"\n"); ausgabe.append (" <kommentar>\n"); for (int i = 0; i < tm.kommentar.length ; i++) {ausgabe.append(" "+element("zeile",tm.kommentar[i])+"\n");} ausgabe.append (" </kommentar>\n"); for (int i = 0; i < tm.bandbeschriftung.length ; i++) { ausgabe.append (" <band>\n"); ausgabe.append (" "+element("schritte", ""+tm.bandbeschriftung[i].schrittzahl) + " " + element ("startposition", ""+tm.bandbeschriftung[i].indexBeschriftungsstart)+ " " + element ("kopfposition", ""+tm.bandbeschriftung[i].indexKopf)+ "\n " + element ("inschrift", tm.bandbeschriftung[i].bandinschrift)+" " + element ("zeile", tm.bandbeschriftung[i].kommentar) +"\n"); ausgabe.append (" </band>\n"); } ausgabe.append("</turingmaschine>\n"); ausgabe.close(); } catch(Exception e) { meldung = meldung + "** Fehler beim Schreiben/Speichern der XML-Datei 1 '"+datname+"': **\n ["+e+"]\n"; System.out.println (meldung); } // end of try } |
Zu vorstehender Methode speichere1 gehören noch zwei Hilfsmethoden, wobei umschreiben dafür sorgt, dass Kleiner- und Größerzeichen im Inhalts-Text in < und > verwandelt werden, damit sie nicht mit den spitzen Klammern um Tags verwechselt werden. Weil das &-Zeichen dadurch aber den Beginn eines Sonderzeichens markiert, muss ein normales & ebenfalls (nämlich in &) umgewandelt werden (lt=lower-than, gt=greater-than, amp=ampersand).
private String element (String tag, String inhalt) { return ("<"+tag+">"+umschreiben(inhalt)+"</"+tag+">"); } |
private String umschreiben (String eingabe) { char z; String s; String ausgabe = ""; for (int i=0; i < eingabe.length(); i++) { z = eingabe.charAt(i); s = ""+z; switch (z) { case '<' : s = "<"; break; case '>' : s = ">"; break; case '&' : s = "&"; break; // weil & sonst Beginn von > oder < } ausgabe = ausgabe + s; } return ausgabe; } |
Das Speichern bezieht seine Inhalte natürlich aus den Datenfeldern (auch globale Variable oder [Klassen-]Attribute genannt) des als Parameter übergebenen Turingmaschinen-Objekts tm, braucht zwar einige Programmzeilen, ist aber nicht übermäßig kompliziert und hoffentlich einsichtig. Es funktioniert problemlos.
Anspruchsvoller und damit aufwändiger ist hingegen das (Wieder-)Einlesen der Daten aus einer bereits vorhandenen bzw. früher gespeicherten XML-Datei. Hier muss der Datenstrom analysiert werden, damit sinntragende Einheiten (wie die Tags oder die Inhalte dazwischen) erkannt werden, der eingelesene Text also in sinnvolle Abschnitte zerlegt wird, und die Abschnitte (Token) dann den entsprechenden Datenfeldern der zu erzeugenden Turingmaschine zugeordnet und - evtl. erst nach Umwandlung des Textes in eine Buchstabenreihung oder in eine Zahl - auch einzeln zugewiesen werden. Es muss also ein Parser programmiert werden (to parse = zerlegen, aufgliedern). Zum Glück ist der Aufbau der XML-Datei im Wesentlichen festgelegt, sodass - anders als etwa beim Maschinensprachen-Parser auf meiner Seite "Informatik mit Java, Teil h)" - hier keine Case-Anweisungen und kein rekursiver Abstieg nötig ist, sondern eigentlich klar ist, welche Textteile gerade erwartet werden können. Offen bleibt lediglich, wie viele Zustände oder Einträge kommen bzw. ob oder wie viele Bandbeschriftungen zusätzlich gespeichert sind. Entsprechend ergibt sich folgender, leider etwas längerer Java-Quelltext:
185 public Turing_zum_Speichern öffne1 (String datname) // gibt vom Datenträger gelesene Turingmaschine als Funktionswert zurück
186 {
187 meldung = ""; // nimmt Warnungen oder Fehlermeldungen beim Lesen oder Parsen auf
188 eingabe1 = ""; // Inhalt der XML-Datei; wird später bei der
Bearbeitung verkürzt (leider gobal)
189 String inhalt; // für den Inhalt zwischen Anfangs- und Endtag eines Elements
190 Turing_zum_Speichern tm = null; // dieses Objekt erhält später die Turingmaschine, die ..
191 try // .. dem Inhalt der XML-Datei entsprechend erzeugt wird
192 {
193 // ----------- Lesen der XML-Datei: -----------------------------------------------
194
195 FileInputStream vonDisk = new FileInputStream (datname);
196 BufferedReader ein = new BufferedReader(new InputStreamReader (vonDisk, "UTF-8"));
197 while (ein.ready())
198 {
199 eingabe1 = eingabe1 + ein.readLine();
200 } // end of while
201 ein.close(); // gesamter Inhalt der XML-Datei wurde in den langen String eingabe1
202 // kopiert, der im Folgenden von vorne an bearbeitet (geparst) und dabei immer auf
203 // den noch unbearbeiteten Rest verkürzt wird.
204
205 // ----------- Verarbeiten des gelesenen Inhalts: ---------------------------------
206
207 if (!(eingabe1.startsWith("<?xml version=\"1.0\" encoding=\"utf-8\"?>")
208 ||eingabe1.startsWith("<?xml version=\"1.0\" encoding=\"UTF-8\"?>")))
209 {
210 meldung = meldung + "** Warnung: Header der gelesenen XML-Datei nicht korrekt **\n";
211 }
212 if (!eingabe1_vorBis("<turingmaschine>"))
213 {
214 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Turingmaschine **\n";
215 return (null);
216 }
217 if (!eingabe1_vorBis("<!-- Gespeicherte Turingmaschine für Programm \"TuringMa_J\", www.r-krell.de/if-t-turi.htm -->"))
218 {
219 meldung = meldung + "** Warnung: falscher oder fehlender Kommentar zur Software in XML-Datei **\n";
220 }
221
222 inhalt = eingabe1_liesInhalt ("bandalphabet");
223
224 if ((inhalt==null)||(inhalt.equals("")))
225 {
226 meldung = meldung + "** Fehlerhafte XML-Datei -- kein Bandalphabet **\n";
227 return (null);
228 }
229 char[] abc = inhalt.toCharArray();
230
231 if (!eingabe1_vorBis("<zustaende>"))
232 {
233 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Zustände **\n";
234 return (null);
235 }
236 ArrayList<String> zwischenspeicher = new ArrayList<String>();
237 int zustandszähler = 0;
238 inhalt = eingabe1_liesInhalt ("zustand");
239 while (inhalt != null)
240 {
241 zustandszähler++;
242 zwischenspeicher.add(inhalt);
243 inhalt = eingabe1_liesInhalt ("zustand");
244 }
245 String[] zustände = new String[zustandszähler];
246 Iterator itzwsp = zwischenspeicher.iterator();
247 for (int i = 0; i < zustandszähler; i++)
248 {
249 zustände[i] = (String)itzwsp.next();
250 }
251 if (!eingabe1_vorBis("</zustaende>"))
252 {
253 meldung = meldung + "** Warnung: Zustände nicht ordnungsgemäß beendet in XML-Datei **\n";
254 }
255
256 inhalt = eingabe1_liesInhalt ("kopfbewegung");
257 if ((inhalt==null)||(inhalt.equals("")))
258 {
259 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Kopfbewegungssymbole **\n";
260 return (null);
261 }
262 char[] richtungen = inhalt.toCharArray();
263
264 if (!eingabe1_vorBis("<maschinentafel>"))
265 {
266 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Maschinentafel **\n";
267 return (null);
268 }
269 Turing_Eintrag[][] tabelle = new Turing_Eintrag[abc.length][zustände.length-1];
270 char altZeichen, neuZeichen;
271 String altZustand, neuZustand;
272 int kopfbew;
273 boolean okay = true;
274 while (okay && eingabe1_vorBis("<eintrag>"))
275 {
276 okay = false;
277 if (eingabe1_vorBis("<alt>"))
278 {
279 altZeichen = eingabe1_liesInhalt("zeichen").charAt(0);
280 altZustand = eingabe1_liesInhalt("zustand");
281 if (eingabe1_vorBis("</alt>") && eingabe1_vorBis("<neu>"))
282 {
283 neuZeichen = eingabe1_liesInhalt ("zeichen").charAt(0);
284 kopfbew = Integer.parseInt (eingabe1_liesInhalt("bewegung"));
285 neuZustand = eingabe1_liesInhalt ("zustand");
286 if (eingabe1_vorBis("</neu>") && eingabe1_vorBis("</eintrag>"))
287 {
288 int x = abc.length-1;
289 while (altZeichen != abc[x])
290 {
291 x--;
292 }
293 int y = zustände.length-1;
294 while (!altZustand.equals(zustände[y]))
295 {
296 y--;
297 }
298 tabelle[x][y] = new Turing_Eintrag (neuZeichen, kopfbew, neuZustand);
299 okay = true;
300 }
301 }
302 }
303 }
304 if (!okay || !eingabe1_vorBis("</maschinentafel>"))
305 {
306 meldung = meldung + "** Fehlerhafte XML-Datei -- falscher Eintrag in oder fehlerhaftes Ende der Maschinentafel **\n";
307 return (null);
308 } // end of if
309
310 int opt = Integer.parseInt (eingabe1_liesInhalt("option"));
311
312 String[] anmerkung = null;
313 if (eingabe1_vorBis("<kommentar>"))
314 {
315 zwischenspeicher = new ArrayList<String>();
316 int zeilenzähler = 0;
317 inhalt = eingabe1_liesInhalt("zeile");
318 while (inhalt != null)
319 {
320 zwischenspeicher.add(inhalt);
321 zeilenzähler++;
322 inhalt = eingabe1_liesInhalt("zeile");
323 } // end of while
324 anmerkung = new String[zeilenzähler];
325 itzwsp = zwischenspeicher.iterator();
326 for (int i = 0; i < zeilenzähler; i++)
327 {
328 anmerkung[i] = (String)itzwsp.next();
329 }
330 if (!eingabe1_vorBis("</kommentar>"))
331 {
332 meldung = meldung + "** Fehlerhafte XML-Datei -- Kommentar endet nicht korrekt **\n";
333 return (null);
334 }
335 }
336
337 ArrayList<Turing_Band> bandspeicher = new ArrayList<Turing_Band>();
338 int bandzähler = 0;
339 while (eingabe1_vorBis("<band>"))
340 {
341 int schritte, start, kopfpos;
342 String inschrift, anmerk;
343 inhalt = eingabe1_liesInhalt("schritte");
344 if ((inhalt==null)||(inhalt.equals("")))
345 {
346 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Schrittzahl im Band **\n";
347 return (null);
348 }
349 schritte = Integer.parseInt(inhalt);
350 inhalt = eingabe1_liesInhalt("startposition");
351 if ((inhalt==null)||(inhalt.equals("")))
352 {
353 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Startposition im Band **\n";
354 return (null);
355 }
356 start = Integer.parseInt(inhalt);
357 inhalt = eingabe1_liesInhalt("kopfposition");
358 if ((inhalt==null)||(inhalt.equals("")))
359 {
360 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Kopfposition im Band **\n";
361 return (null);
362 }
363 kopfpos = Integer.parseInt(inhalt);
364 inhalt = eingabe1_liesInhalt("inschrift");
365 if ((inhalt==null)||(inhalt.equals("")))
366 {
367 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Bandinschrift im Band **\n";
368 return (null);
369 }
370 inschrift = inhalt;
371 inhalt = eingabe1_liesInhalt("zeile");
372 if ((inhalt==null))
373 {
374 meldung = meldung + "** Fehlerhafte XML-Datei -- kein Kommentar im Band **\n";
375 return (null);
376 }
377 anmerk = inhalt;
378 if (!eingabe1_vorBis("</band>"))
379 {
380 meldung = meldung + "** Fehlerhafte XML-Datei -- Bandende nicht gefunden **\n";
381 return (null);
382 }
383 bandzähler++;
384 bandspeicher.add(new Turing_Band (schritte, start, kopfpos, inschrift, anmerk));
385 }
386 if (!eingabe1_vorBis("</turingmaschine>"))
387 {
388 meldung = meldung + "** Fehlerhafte XML-Datei -- Turingmaschine endet nicht korrekt **\n";
389 return (null);
390 }
391
392 Turing_Band[] bänder = new Turing_Band[bandzähler];
393 Iterator itbsp = bandspeicher.iterator();
394 for (int i = 0; i < bandzähler; i++)
395 {
396 bänder[i] = (Turing_Band)itbsp.next();
397 }
398
399 // ----------- Erzeugen der Turingmaschine aus den verarbeiteten Daten: ----------
400
401 tm = new Turing_zum_Speichern (abc, zustände, richtungen, tabelle, opt, anmerkung, bänder);
402 }
403 catch (Exception e) // Fehler beim Lesen oder bei parseInt oder bei zwischen-/bandspeicher-Leeren
404 {
405 meldung = meldung + "** Fehler beim Öffnen/Lesen der XML-Datei 1 '"+datname+"': **\n ["+e+"]\n";
406 System.out.println (meldung);
407 }
408
409 return(tm); // Ergebnis tm = aus XML-Datei eingelesene Turingmaschine
410 }
411
Auch für diese Lese-Methode wurden - hier drei - Hilfsmethoden definert:
412 public String eingabe1_liesInhalt (String tag) // liefert zurückverwandelten Inhalt zwischen <tag> und </tag>
413 { // ..
aus eingabe1 und verkürzt eingabe1; null bei Fehler
414 eingabe1 = eingabe1.trim(); // entfernt (führende) Leerstellen, Tabs, Zeilenumbrüche..
415 while (eingabe1.indexOf("<!--")==0) // entfernt Kommentare bis zum nächsten Tag
416 {
417 int ende = eingabe1.indexOf("-->");
418 eingabe1 = eingabe1.substring(ende+3).trim();
419 }
420 String gefunden = "";
421 if (eingabe1.indexOf("<"+tag+">") != 0) // Tag nicht wie erwartet vorne
422 {
423 return (null);
424 }
425 eingabe1 = eingabe1.substring(eingabe1.indexOf('>')+1); // Anfangs-Tag abschneiden
426 int ende = eingabe1.indexOf("</"+tag+">"); // End-Tag suchen
427 if (ende < 0) // kein End-Tag gefunden
428 {
429 meldung = meldung + "** Fehler: End-Tag </"+tag+"> nicht gefunden! **\n";
430 return (null);
431 }
432 gefunden = eingabe1.substring(0, ende); // Text zwischen Anfangs- und End-Tag
433 gefunden = muschreiben (gefunden); // evtl. Umschreibungen zurück verwandeln
434 eingabe1 = eingabe1.substring (eingabe1.indexOf('>')+1); // gelesenen Text inkl. End-Tag entfernen
435 return (gefunden);
436 }
437
438 private String muschreiben (String vorher) // Gegenstück zu umschreiben: >, < und & wieder richtig
439 {
440 String nachher = "";
441 char bst;
442 for (int i=0; i<vorher.length(); i++)
443 {
444 bst = vorher.charAt(i);
445 if (bst=='&')
446 {
447 String umschrift = "";
448 int j=i+1;
449 while (j<vorher.length() && (vorher.charAt(j) != ';'))
450 {
451 umschrift = umschrift + vorher.charAt(j);
452 j++;
453 }
454 switch (umschrift)
455 {
456 case "lt" : bst = '<'; i = j; break;
457 case "gt" : bst = '>'; i = j; break;
458 case "amp" : bst = '&'; i = j; break;
459 default : bst = '§'; meldung = meldung + "** Warnung: Unerlaubtes & an Stelle von § gefunden **\n";
460 break; // statt &, das hier nicht stehen durfte
461 }
462 }
463 nachher = nachher + bst;
464 } // end of for
465 return (nachher);
466 }
467
468 private boolean eingabe1_vorBis (String zeichenkette) // schneidet vorne von eingabe1 alles bis einschl.
469 { // der zeichenkette ab; false, falls zeichenkette nicht gefunden
470 int i = eingabe1.indexOf(zeichenkette);
471 if (i>=0)
472 {
473 eingabe1 = eingabe1.substring(i+zeichenkette.length());
474 }
475 return (i>=0);
476 }
Dabei mussten leider die beiden String-Variablen eingabe1 und meldung global in der umhüllenden Klasse definiert werden, damit Änderungen durch die Hilfsmethode auch wirklich auf die eingabe1 innerhalb von öffne1 wirken. Obwohl Strings eigentlich Objekte sind, wird etwa an eine Methode mit dem Header private boolean vor_Bis (String ein, String zeichenkette) beim Aufruf vor_Bis (eingabe1, "!"); nicht die Objektreferenz von eingabe1 an ein übergeben (sodass beide Namen auf das selbe Objekt zeigen), sondern es wird wie bei primitiven Datentypen der Inhalt von eingabe1 in eine neue Variable ein kopiert. Änderungen an ein innerhalb von vor_Bis würden dann nur auf diese Kopie wirken, während das Original eingabe1 unverändert bliebe.
Außerdem mussten in den Zeilen 236, 315 und 337 zusätzliche ArrayListen definiert werden, um die zunächst noch unbekannte Anzahl von Zuständen, Kommentarzeilen und Bändern aufzunehmen. Anders als die statischen Reihungen in meiner Turingmaschine nach dem Bauplan Turing_zum_Speichern können ArrayListen bei Bedarf wachsen. Ein Zähler zählt jeweils die gelesenen Elemente mit, damit anschließend die Reihung entsprechend dimensioniert und die Elemente aus der ArrayList endgültig in die nun passende Reihung kopiert werden können (z.B. Zeilen 245 bis 250, 324 bis 329 und 392 bis 397). Will man das Zwischenspeichern vermeiden, hätte man die Datenfelder der Turingmaschine dynamisch statt statisch anlegen müssen. Oder man könnte die Start-Tags um ein [XML-]Attribut erweitern, d.h. z.B. statt <zustaende> ein erweitertes Tag <zustaende anzahl="5"> verwenden, das die Größe der Reihung angibt und so das Definieren erlaubt, damit anschließend die 5 noch zu lesenden Zustände direkt passend aufgenommen werden können. Damit ArrayListen benutzt werden können, muss der Klasse noch import java.util.*; vorangestellt werden (s.u., Zeile 002).
Für jedes Datenfeld der zu erzeugenden Turingmaschine wird zunächst eine eigene Variable als Zwischen- oder Hilfsspeicher verwendet; erst am Ende von öffne1 werden in der Zeile 401 alle diese nach und nach definierten und gefüllten lokalen Variablen an den Konstruktor der Turingmaschine übergeben, um die tm als Ganzes zu erzeugen und in Zeile 409 als Ergebnis bzw. Funktionswert an die aufrufende Stelle zurück zu geben. Der global definierte String eingabe1 ist nach der Zeile 200 zunächst mit dem vollständigen Inhalt der XML-Datei gefüllt und wir nachfolgend von vorne nach hinten geparst. Jeder erkannte Teiltext wird von eingabe1 vorne abgeschnitten, sodass eingabe1 in gleichem Maße verkürzt wird, wie andere Variablen angelegt und gefüllt werden.
Mit normalen Dateioperationen und String-Befehlen lässt sich also ohne Weiteres jede Turingmaschine in einer XML-Datei speichern und auch wieder sicher öffnen. Um mehr Zukunftssicherheit zu erreichen gegenüber der im Kapitel A oben auf dieser Seite gezeigten Speicherung als Java-Objekt, waren allerdings viel mehr Programmzeilen nötig. Anders als beim Speichern in Datenbanken (Kapitel B) sind wie in A keine Fremdprogramme oder Netzwerkzugriffe nötig. Da ab etwa 2004/2005 (beginnend mit "Java 5") zunehmend Bibliotheken für den Umgang mit XML-Dateien in Java integriert wurden, kann deren Verwendung vielleicht noch Programmieraufwand sparen. Das soll im Folgenden versucht werden.
zum Seitenanfang / zum Seitenende
Wie eben erwähnt, wurden mittlerweise viele Bibliotheken in Java integriert, die das Programmieren von Anwendungen mit Zugriff auf XML-Dateien erleichtern sollen. Dabei wurden mindestens vier verschiedene Konzepte realisiert:
Nach dem Import der nötigen Bibliotheken in den Zeilen 003 bis 005
001 import java.io.*;
002 import java.util.*; // für ArrayList in öffne1
003 import javax.xml.*;
004 import javax.xml.stream.*;
005 import javax.xml.stream.events.*;
006
007
008 public class Turing_XML_Speichertest // Demo: Speichern und Öffnen einer Turingmaschine als
XML-Datei. r-krell.de, 8/2019
009 {
010 String meldung = ""; // für Warnungen und Fehler beim Speichern und/oder Öffnen
011 String eingabe1 = ""; // für öffne1 und eingabe1_liesInhalt sowie eingabe1_vorBis
012
013 public Turing_XML_Speichertest() // Konstruktor mit Anweisungen für einen Testlauf
014 {
015 Turing_zum_Speichern tm0 = beispiel_bitaddition_kurz();
016 zeige (tm0);
017 speichere1 (tm0, "turi$1$$.xml"); // schreibt auf den Datenträger
018 speichere2 (tm0, "turi$2$$.xml");
019
020 System.out.println("###### öffne1 #####");
021 Turing_zum_Speichern tm1 = öffne1 ("turi$1$$.xml"); // liest vom Datenträger
022 System.out.println("meldung = ["+meldung+"]");
023 zeige (tm1);
024 System.out.println("###### öffne2a #####");
025 Turing_zum_Speichern tm2a = öffne2a ("turi$2$$.xml");
026 System.out.println("meldung = ["+meldung+"]");
027 // zeige (tm2a);
028 System.out.println("###### öffne2b #####");
029 Turing_zum_Speichern tm2b = öffne2b ("turi$2$$.xml");
030 System.out.println("meldung = ["+meldung+"]");
031 zeige (tm2b);
032 }
033
folgt ab Zeile 482 zunächst die StAX-Variante des Speicherns. Die Methode wird aus der Zeile 018 heraus aufgerufen:
482 public void speichere2 (Turing_zum_Speichern tm, String datname) // schreibt tm per StAX-Befehlen als XML auf Datenträger
483 {
484 try
485 {
486 XMLOutputFactory factory = XMLOutputFactory.newInstance();
487 XMLStreamWriter writer = factory.createXMLStreamWriter (new FileOutputStream(datname), "UTF-8");
488 writer.writeStartDocument("UTF-8","1.0"); // Der XML-Header wird erzeugt
489 writer.writeCharacters ("\n");
490 writer.writeStartElement ("turingmaschine"); // Zuerst wird das Wurzelelement mit Attribut geschrieben
491 writer.writeCharacters ("\n ");
492 writer.writeComment (" Gespeicherte Turingmaschine für Programm \"TuringMa_J\", www.r-krell.de/if-t-turi.htm ");
493 element2 (writer, "\n ", "bandalphabet", new String(tm.bandalphabet), " "); // ersetzt folgende 5 Zeilen:
494 // writer.writeCharacters ("\n ");
495 // writer.writeStartElement ("bandalphabet");
496 // writer.writeCharacters (new String(tm.bandalphabet));
497 // writer.writeEndElement (); // /bandalphabet
498 // writer.writeCharacters (" ");
499 writer.writeComment ("erstes Zeichen ist das Leerzeichen");
500 writer.writeCharacters ("\n ");
501 writer.writeStartElement ("zustaende");
502 writer.writeComment ("erster Zustand ist Startzustand, letzter ist Haltzustand");
503 writer.writeCharacters ("\n ");
504 for (int y = 0; y < tm.zustand.length; y++)
505 {
506 element2 (writer, "", "zustand", tm.zustand[y], " ");
507 }
508 writer.writeCharacters ("\n ");
509 writer.writeEndElement(); // /zustände
510 element2 (writer, "\n ", "kopfbewegung", new String(tm.kopfbewegung), "\n ");
511 writer.writeStartElement ("maschinentafel");
512 for (int y = 0; y < tm.zustand.length-1; y++)
513 {
514 for (int x = 0; x < tm.bandalphabet.length; x++)
515 {
516 if (tm.maschinentafel[x][y]!=null)
517 {
518 writer.writeCharacters ("\n ");
519 writer.writeStartElement ("eintrag");
520 writer.writeCharacters (" ");
521 writer.writeStartElement ("alt");
522 element2 (writer, "", "zeichen", ""+tm.bandalphabet[x], "");
523 element2 (writer, "", "zustand", ""+tm.zustand[y], "");
524 writer.writeEndElement(); // /alt
525 writer.writeCharacters ("\n ");
526 writer.writeStartElement ("neu");
527 element2 (writer, "", "zeichen", ""+tm.maschinentafel[x][y].neuesZeichen, "");
528 element2 (writer, "", "bewegung", ""+tm.maschinentafel[x][y].kopfbewegung, "");
529 element2 (writer, "", "zustand", ""+tm.maschinentafel[x][y].neuerZustand, "");
530 writer.writeEndElement(); // /neu
531 writer.writeCharacters (" ");
532 writer.writeEndElement(); // /eintrag
533 }
534 }
535 }
536 writer.writeCharacters ("\n ");
537 writer.writeEndElement(); // /maschinentafel
538 element2 (writer, "\n ", "option", ""+tm.reihenfolgenopt, "\n ");
539 writer.writeStartElement ("kommentar");
540 for (int i = 0; i < tm.kommentar.length ; i++)
541 { element2 (writer, "\n ", "zeile", tm.kommentar[i], ""); }
542 writer.writeCharacters ("\n ");
543 writer.writeEndElement(); // /kommentar
544
545 for (int i = 0; i < tm.bandbeschriftung.length ; i++)
546 {
547 writer.writeCharacters ("\n ");
548 writer.writeStartElement ("band");
549 element2 (writer, "\n ", "schritte", ""+tm.bandbeschriftung[i].schrittzahl, " ");
550 element2 (writer, "", "startposition", ""+tm.bandbeschriftung[i].indexBeschriftungsstart, " ");
551 element2 (writer, "", "kopfposition", ""+tm.bandbeschriftung[i].indexKopf, "\n ");
552 element2 (writer, "", "inschrift", tm.bandbeschriftung[i].bandinschrift, " ");
553 element2 (writer, "", "zeile", tm.bandbeschriftung[i].kommentar, "\n ");
554 writer.writeEndElement(); // /band
555 }
556
557 writer.writeCharacters ("\n");
558 writer.writeEndElement(); // /turingmaschine
559 writer.writeEndDocument();
560 writer.close();
561 }
562 catch (Exception e)
563 {
564 meldung = meldung + "** Fehler beim Schreiben/Speichern der XML-Datei 2 '"+datname+"': **\n ["+e+"]\n";
565 System.out.println (meldung);
566 } // end of try
567 }
568
und auch hier wurde eine selbst geschriebene Hilfsmethode verwendet, um den vorstehenden Code abzukürzen:
569 private void element2 (XMLStreamWriter w, String vorher, String tag, String inhalt, String nachher) throws XMLStreamException
570 { // fasst häufig wiederkehrende Befehlsfolge zusammen, um vorher+<tag>+inhalt+</tag>+nachher zu schreiben
571 if ((vorher!=null) && (vorher.length()>0))
572 { w.writeCharacters (vorher); }
573 w.writeStartElement (tag);
574 w.writeCharacters (inhalt); // writeCaracters ersetzt <, > und & automatisch durch <, >, und &
575 w.writeEndElement ();
576 if ((nachher!=null) && (nachher.length()>0))
577 { w.writeCharacters (nachher); }
578 }
Das XMLStreamWriter-Objekt im Parameter wird als Objekt-Referenz auf das Original-Objekt übergeben, sodass Änderungen an w durch die bzw. innerhalb der Methode element2 direkt das Objekt writer in der übergeordneten Methode speichere2 betreffen. Anders als für eingabe1 in Cb1) muss keine unschöne globale Variable verwendet werden.
Auch dieses Programmstück funktioniert wie gewünscht; es ist allerdings keineswegs kürzer als die Methode speichere1 in Cb1), zumal die StAX-Bibliothek keine Hilfe für ein schönes Formatieren der XML-Datei bereithält. Vielmehr muss die Programmiererin oder muss der Programmierer selbst für Leerzeichen (oder Tabulatoren) fürs Einrücken und für die Zeilenumbrüche sorgen. Diese Arbeit wurde hier mit vorher und nachher oft in der Hilfsmethode element2 vorgenommen. Allerdings ist die schöne Darstellung vor allem für das menschliche Auge bestimmt - die maschinelle Lesbarkeit beim Öffnen wird davon nicht verbessert.
Für das (Wieder-)Einlesen einer bereits gespeicherten XML-Datei und die Rückverwandlung in eine Turingmaschine hält die StAX-Bibliothek hingegen sogar zwei Unter-Konzepte bereit. Entweder kann der Parser die erkannten Einheiten als Ereignis ansehen, wie hier in öffne2a:
public Turing_zum_Speichern öffne2a (String datname) // gibt vom Datenträger gelesene Turingmaschine als Funktionswert zurück // liest XML-Datei mit StAX-Mitteln (d.h. Streaming-API) event-orientiert ein und erzeugt tm { meldung = ""; // nimmt Warnungen oder Fehlermeldungen beim Lesen oder Parsen auf Turing_zum_Speichern tm = null; // dieses Objekt erhält später die Turingmaschine, die .. try // .. dem Inhalt der XML-Datei entsprechend erzeugt wird { XMLInputFactory factory = XMLInputFactory.newInstance (); XMLEventReader parser = factory.createXMLEventReader (new FileInputStream(datname), "UTF-8"); XMLEvent event = parser.nextEvent(); if (!event.isStartDocument()) // oder if (event.getEventType()!=XMLStreamConstants.START_DOCUMENT) { meldung = meldung + "** Warnung: Header der gelesenen XML-Datei nicht erkannt bzw. nicht korrekt **\n"; } do { event = parser.nextEvent(); } while (!event.isStartElement()); // überliest Kommentare // oder .. } while (!(event.getEventType()==XMLStreamConstants.START_ELEMENT)); if (!event.asStartElement().getName().getLocalPart().equals("turingmaschine")) { meldung = meldung + "** Fehlerhafte XML-Datei -- keine Turingmaschine **\n"; return (null); } // Kommentar kann mit StAX-Mitteln nicht gelesen werden, da kein eigenes Event. Insofern hier keine Überprüfung // auf "<!-- Gespeicherte Turingmaschine für Programm "TuringMa_J", www.r-krell.de/if-t-turi.htm -->" do { event = parser.nextEvent(); } while (!event.isStartElement()); // überliest Attribute, Leerzeichen, Zeilenvorschübe, Kommentare if (!event.asStartElement().getName().getLocalPart().equals("bandalphabet")) { meldung = meldung + "** Fehlerhafte XML-Datei -- kein Bandalphabet **\n"; return (null); } event = parser.nextEvent(); char[] abc = (event.asCharacters()).getData().toCharArray(); event = parser.nextEvent(); if(!(event.isEndElement()&&(event.asEndElement().getName().getLocalPart().equals("bandalphabet")))) { meldung = meldung + "** Fehlerhafte XML-Datei -- Bandalphabet endet nicht korrekt **\n"; return (null); } do { event = parser.nextEvent(); } while (!event.isStartElement()); // vor bis zum nächsten Startelement, s.o. if (!event.asStartElement().getName().getLocalPart().equals("zustaende")) { meldung = meldung + "** Fehlerhafte XML-Datei -- keine Zustände **\n"; return (null); } //... jetzt Zustände einlesen, danach Kopfbewegung, Maschinentafel usw. (fehlt hier -- // -- vgl. aber vollständige Methoden öffne1 und öffne2b) .... |
Oder man nutzt die so genannte cursor-orientierte Variante des StAX-Parsers. Es ist wohl eher Geschmackssache, welche Version bevorzugt wird. Mir gefiel letztere Variante etwas besser, weshalb ich sie für den vollständigen Quelltext verwendet habe:
638 public Turing_zum_Speichern öffne2b (String datname) // gibt vom Datenträger gelesene Turingmaschine als Funktionswert zurück
639 // liest XML-Datei mit StAX-Mitteln (d.h. Streaming-API) cursor-orentiert ein und erzeugt tm
640 {
641 // ----------- elementweises Lesen und Verarbeiten des XML-Inhalts : ---------------
642 meldung = ""; // nimmt Warnungen oder Fehlermeldungen beim Lesen oder Parsen auf
643 Turing_zum_Speichern tm = null; // dieses Objekt erhält später die Turingmaschine, die ..
644 try // .. dem Inhalt der XML-Datei entsprechend erzeugt wird
645 {
646 XMLInputFactory factory = XMLInputFactory.newInstance ();
647 XMLStreamReader reader = factory.createXMLStreamReader(new FileInputStream(datname), "UTF-8");
648
649 if (!((reader.next()==XMLStreamConstants.START_ELEMENT)&&reader.getVersion().equals("1.0")
650 &&reader.getEncoding().equalsIgnoreCase("UTF-8")))
651 {
652 meldung = meldung + "** Warnung: Header der gelesenen XML-Datei nicht erkannt bzw. nicht korrekt **\n";
653 }
654 if (!reader.getLocalName().equals("turingmaschine"))
655 {
656 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Turingmaschine **\n";
657 return (null);
658 }
659
660 char[] abc = liesInhalt2b(reader,"bandalphabet").toCharArray();
661
662 if (!(nächstesStarttag(reader,"zustaende")))
663 {
664 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Zustände **\n";
665 return (null);
666 }
667 ArrayList<String> zwischenspeicher = new ArrayList<String>();
668 int zustandszähler = 0;
669 while (nächstesStarttag(reader, "zustand"))
670 {
671 zustandszähler++;
672 reader.next();
673 zwischenspeicher.add(reader.getText());
674 reader.next();
675 if (!(reader.isEndElement()&&reader.getLocalName().equals("zustand")))
676 {
677 meldung = meldung + "** Fehlerhafte XML-Datei -- Zustand endet nicht korrekt **\n";
678 return (null);
679 }
680 }
681 String[] zustände = new String[zustandszähler];
682 Iterator itzwsp = zwischenspeicher.iterator();
683 for (int i = 0; i < zustandszähler; i++)
684 {
685 zustände[i] = (String)itzwsp.next();
686 }
687 if (!(reader.isEndElement()&&reader.getLocalName().equals("zustaende")))
688 {
689 meldung = meldung + "** Warnung: Zustände nicht ordnungsgemäß beendet in XML-Datei **\n";
690 }
691
692 String inhalt = liesInhalt2b (reader, "kopfbewegung");
693 if ((inhalt==null)||(inhalt.equals("")))
694 {
695 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Kopfbewegungssymbole **\n";
696 return (null);
697 }
698 char[] richtungen = inhalt.toCharArray();
699
700 if (!nächstesStarttag(reader, "maschinentafel"))
701 {
702 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Maschinentafel **\n";
703 return (null);
704 }
705
706 Turing_Eintrag[][] tabelle = new Turing_Eintrag[abc.length][zustände.length-1];
707 char altZeichen, neuZeichen;
708 String altZustand, neuZustand;
709 int kopfbew;
710 boolean okay = true;
711 while (okay && nächstesStarttag(reader, "eintrag"))
712 {
713 okay = false;
714 if (nächstesStarttag(reader,"alt"))
715 {
716 altZeichen = liesInhalt2b(reader,"zeichen").charAt(0);
717 altZustand = liesInhalt2b(reader,"zustand");
718 if (nächstesEndtag(reader,"alt")&&nächstesStarttag(reader,"neu"))
719 {
720 neuZeichen = liesInhalt2b(reader,"zeichen").charAt(0);
721 kopfbew = Integer.parseInt (liesInhalt2b(reader,"bewegung"));
722 neuZustand = liesInhalt2b(reader,"zustand");
723
724 if (nächstesEndtag(reader,"neu")&&nächstesEndtag(reader,"eintrag"))
725 {
726 int x = abc.length-1;
727 while (altZeichen != abc[x])
728 {
729 x--;
730 }
731 int y = zustände.length-1;
732 while (!altZustand.equals(zustände[y]))
733 {
734 y--;
735 }
736 tabelle[x][y] = new Turing_Eintrag (neuZeichen, kopfbew, neuZustand);
737 okay = true;
738 }
739 }
740 }
741 }
742 if (!okay || !reader.isEndElement()&&reader.getLocalName().equals("maschinentafel"))
743 {
744 meldung = meldung + "** Fehlerhafte XML-Datei -- falscher Eintrag in oder fehlerhaftes Ende der Maschinentafel **\n";
745 return (null);
746 } // end of if
747
748 int opt = Integer.parseInt (liesInhalt2b(reader,"option"));
749
750 String[] anmerkung = null;
751 if (nächstesStarttag(reader,"kommentar"))
752 {
753 zwischenspeicher = new ArrayList<String>();
754 int zeilenzähler = 0;
755
756 while (nächstesStarttag(reader,"zeile"))
757 {
758 inhalt = "";
759 reader.next();
760 while (reader.isCharacters())
761 {
762 inhalt = inhalt + reader.getText();
763 reader.next();
764 }
765 zwischenspeicher.add(inhalt);
766 zeilenzähler++;
767 if (!reader.isEndElement()&&reader.getLocalName().equals("zeile"))
768 {
769 meldung = meldung + "** Warnung: Kommentar-Zeile in XML-Datei endet nicht korrekt **\n";
770 }
771 } // end of while
772 anmerkung = new String[zeilenzähler];
773 itzwsp = zwischenspeicher.iterator();
774 for (int i = 0; i < zeilenzähler; i++)
775 {
776 anmerkung[i] = (String)itzwsp.next();
777 }
778 if (!reader.isEndElement()&&reader.getLocalName().equals("kommentar"))
779 {
780 meldung = meldung + "** Warnung: Kommentar in XML-Datei endet nicht korrekt **\n";
781 }
782 }
783
784 ArrayList<Turing_Band> bandspeicher = new ArrayList<Turing_Band>();
785 int bandzähler = 0;
786 while (nächstesStarttag(reader, "band"))
787 {
788 int schritte, start, kopfpos;
789 String inschrift, anmerk;
790 inhalt = liesInhalt2b (reader, "schritte");
791 if ((inhalt==null)||(inhalt.equals("")))
792 {
793 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Schrittzahl im Band **\n";
794 return (null);
795 }
796 schritte = Integer.parseInt(inhalt);
797 inhalt = liesInhalt2b (reader, "startposition");
798 if ((inhalt==null)||(inhalt.equals("")))
799 {
800 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Startposition im Band **\n";
801 return (null);
802 }
803 start = Integer.parseInt(inhalt);
804 inhalt = liesInhalt2b (reader, "kopfposition");
805 if ((inhalt==null)||(inhalt.equals("")))
806 {
807 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Kopfposition im Band **\n";
808 return (null);
809 }
810 kopfpos = Integer.parseInt(inhalt);
811 inhalt = liesInhalt2b (reader, "inschrift");
812 if ((inhalt==null)||(inhalt.equals("")))
813 {
814 meldung = meldung + "** Fehlerhafte XML-Datei -- keine Bandinschrift im Band **\n";
815 return (null);
816 }
817 inschrift = inhalt;
818 inhalt = liesInhalt2b (reader, "zeile");
819 if ((inhalt==null))
820 {
821 meldung = meldung + "** Fehlerhafte XML-Datei -- kein Kommentar im Band **\n";
822 return (null);
823 }
824 anmerk = inhalt;
825 if (!nächstesEndtag (reader, "band"))
826 {
827 meldung = meldung + "** Fehlerhafte XML-Datei -- Bandende nicht gefunden **\n";
828 return (null);
829 }
830 bandzähler++;
831 bandspeicher.add(new Turing_Band (schritte, start, kopfpos, inschrift, anmerk));
832 }
833 if (!reader.isEndElement()&&reader.getLocalName().equals("turingmaschine"))
834 {
835 meldung = meldung + "** Fehlerhafte XML-Datei -- Turingmaschine endet nicht korrekt **\n";
836 return (null);
837 }
838
839 Turing_Band[] bänder = new Turing_Band[bandzähler];
840 Iterator itbsp = bandspeicher.iterator();
841 for (int i = 0; i < bandzähler; i++)
842 {
843 bänder[i] = (Turing_Band)itbsp.next();
844 }
845
846 // ----------- Erzeugen der Turingmaschine aus den verarbeiteten Daten: ----------
847
848 tm = new Turing_zum_Speichern (abc, zustände, richtungen, tabelle, opt, anmerkung, bänder);
849
850 }
851 catch (Exception e)
852 {
853 meldung = meldung + "** Fehler beim Öffnen/Lesen der XML-Datei 2b '"+datname+"': **\n ["+e+"]\n";
854 System.out.println (meldung);
855 }
856 return (tm);
857 }
858
Dabei wurden drei Hilfsmethoden definiert und in öffne2b verwendet:
859 private boolean nächstesStarttag (XMLStreamReader sreader, String tag) throws XMLStreamException
860 {
861 sreader.nextTag(); // überliest Leerzeichen, Zeilenvorschübe, Kommentare.. und geht zum nächsten Start- oder End-Tag
862 return (sreader.isStartElement()&&sreader.getLocalName().equals(tag)); // true,
falls Starttag mit richtigem Namen
863 }
864
865 private boolean nächstesEndtag (XMLStreamReader sreader, String tag) throws XMLStreamException
866 {
867 sreader.nextTag(); // überliest Leerzeichen, Zeilenvorschübe, Kommentare.. und geht zum nächsten Start- oder End-Tag
868 return (sreader.isEndElement()&&sreader.getLocalName().equals(tag)); // true,
falls Endtag mit richtigem Namen
869 }
870
871 private String liesInhalt2b (XMLStreamReader sreader, String tag) throws XMLStreamException
872 {
873 String inhalt = "";
874 if (!nächstesStarttag(sreader,tag))
875 {
876 meldung = meldung + "** Fehlerhafte XML-Datei -- <"+tag+"> nicht an erwarteter Stelle **\n";
877 }
878 sreader.next();
879 while (sreader.isCharacters())
880 { // Wiederholstruktur nötig,
881 inhalt = inhalt + sreader.getText(); // weil getText bei Sonderzeichen wie > vorzeitig endet
882 sreader.next(); // Sonderzeichen bilden eigene Einheit, werden aber rückübersetzt
883 }
884 if (!sreader.isEndElement()&&sreader.getLocalName().equals(tag))
885 {
886 meldung = meldung + "** Fehlerhafte XML-Datei -- </"+tag+"> nicht an erwarteter Stelle **\n";
887 }
888 return (inhalt);
889 }
Ähnlich wie schon bei speichere2 erhalten die Hilfsmethoden hier den XMLStreamReader als Objektreferenz, sodass reader - anders als eingabe1 in öffne1 - nicht global definiert werden muss und sreader trotzdem auf die selbe, in Zeile 647 definierte und erzeugte Variable zugreift. Ansonsten unterscheidet sich die Methode öffne2b im nötigen Aufbau eigentlich nicht von öffne1 aus Cb1). Mit 251 Codezeilen (inkl. Hilfsmethoden) ist öffne2 trotz Verwendung der XML-Streaming-Bibliothek auch nur unwesentlich kürzer als das herkömmlich programmierte öffne1 (292 Zeilen Programmtext). Insofern lohnt sich die Verwendung der XML-Bibliotheken der ersten drei Konzepte kaum - offenbar ein Makel, der auch den Javaentwicklern bei Sun bzw. Oracle zu Denken gab. Insofern wurde mit dem nachfolgend getesten JAXB-Konzept noch ein weiterer Weg beschritten.
zum Seitenanfang / zum Seitenende
Das Speichern und Wiedereinlesen eines Java-Objekts in/aus eine(r) XML-Datei gehört vermutlich zu den häufigeren Standardaufgaben moderner Anwendungen. Deshalb ist es ärgerlich, wenn der Anwendungsprogrammierer sein Objekt - hier Turing_zum_Speichern - immer von Hand aufdröseln und Datenfeld für Datenfeld in XML-Häppchen übersetzen oder parsen muss, um so langwierig die XML-Datei zu erzeugen bzw. daraus wieder das Objekt zurück zu gewinnen. Wünschenswert wäre, wenn die Umsetzung praktisch im Hintergrund automatisch geschehen könnte und das Speichern oder Öffnen mit XML ähnlich einfach zu programmieren wäre, wie das direkte Speichern oder Einlesen des Objekts oben im Abschnitt Ab). Tatsächlich soll dieser Wunsch durch das JAXB-Konzept und die zugehörige Bibliothek erfüllt werden: Das Java-XML-Binding soll ein Java-Objekt mit einer automatisch erzeugten XML-Datei verbinden. Der Vorgang des Erzeugens und Speicherns der XML-Datei wird als Marshalling bezeichnet (to marshal = aufstellen, anordnen); das Öffnen einer XML-Datei und Übertragung der Daten in das Objekt wird Unmarshalling genannt.
Um den automatischen Marshalling-Vorgang zu steuern, müssen beim zu speichernden Objekt zusätzlich eingefügte Annotations angeben, welche Datenfelder mit welchen Tags ummantelt und gespeichert werden sollen. Ein Rootelement-Name am Klassenbeginn ist Pflicht (root = Wurzel). Datenfelder ohne vorangestellte Annotation tauchen in der XML-Datei gar nicht auf. Und für Reihungen bietet sich ein äußeres Wrapper-Tag um die einzelnen Komponenten an (to wrap = umhüllen, einwickeln). Dabei ist eine Annotation eine Anmerkung oder ein besonderer Kommentar mit festgelegter Syntax, der zwar den eigentlichen Programmtext nicht verändert, trotzdem aber vom Compiler oder zur Laufzeit berücksichtigt wird - hier durch Verwendung der vorgeschlagenen Namen und Berücksichtigung der damit gekennzeichneten Datenfelder für den Marshalling-Prozess. Damit würde die Turingmaschinen-Klasse folgende Gestalt annehmen (vgl. Original in Aa); die Annotations beginnen mit @):
import javax.xml.bind.annotation.*; @XmlRootElement(name="turingmaschine") public class Turing_zum_Speichern3 { @XmlElementWrapper(name="bandalphabet") @XmlElement(name="zeichen") char[] bandalphabet; // enthält alle les- und schreibbaren Schriftzeichen; bandalphabet[0] = Leerzeichen @XmlElementWrapper(name="zustaende") @XmlElement(name="zustand") String[] zustand; // Namen aller Zustände; zustand[0] = Startzustand, zutand[maxIndex] = Endzustand @XmlElementWrapper(name="kopfbewegung") @XmlElement(name="symbol") char[] kopfbewegung; // genau drei Zeichen, z.B. 'L','S','R' oder '-','0','+' oder '<','=','>' @XmlElementWrapper(name="maschinentafel") @XmlElement(name="eintrag3") // wirkt nur auf Tabellenzeile Turing_Eintrag3[], trotzdem darin <item>..</item> Turing_Eintrag3[][] maschinentafel; // 2-dim. Tabelle für bandalphabet.length x (zustand.length - 1) Einträge @XmlElement(name="option") int reihenfolgenopt; // Option für Reihenfolge im Turing_Eintrag (möglich: eine der Konstanten aus Turing_Eintrag) @XmlElementWrapper(name="kommentar") @XmlElement(name="zeile") String[] kommentar; // mehrzeilige Beschreibung/Erläuterung zur Turing-Maschine @XmlElementWrapper(name="gespeicherteBaender") @XmlElement(name="band") Turing_Band3[] bandbeschriftung; // Bandinschriften mit Position, Kopfposition und Anzahl der Schritte bis zur Inschrift public Turing_zum_Speichern3 () { } // ("no-arg"-Standard-)Konstruktor wird sonst nicht benutzt, ist aber nötig für JAXB-Marshalling public Turing_zum_Speichern3 (char[] abc, String[] zustände, char[] richtungen, Turing_Eintrag3[][] tabelle, int opt, String[] anmerkung, Turing_Band3[] bänder) { bandalphabet = abc; zustand = zustände; kopfbewegung = richtungen; maschinentafel = tabelle; reihenfolgenopt = opt; kommentar = anmerkung; bandbeschriftung = bänder; } } |
In gleicher Weise wurden die Unterklassen Turing_Eintrag und Turing_Band durch entsprechende Annotations zu Turing_Eintrag3 und Turing_Band3 ergänzt (hier nicht gezeigt).
Jetzt reichen wenige Zeilen Java-Quelltext, um mit der Methode speichere3 die XML-Datei zu
schreiben, die wegen m.setProperty (Marshaller.JAXB_FORMATTED_OUTPUT,... sogar eine schön mit Einrückungen und
Zeilenvorschüben formatierte XML-Datei liefert. Die Methoden speichere1 und speichere2 weiter oben in Cb1) und Cb2)
bestanden aus viel mehr Programmzeilen!).
Noch viel deutlicher unterscheidet sich der Programmieraufwand fürs Lesen: den (mit Hilfsmethoden) fast 300 bzw. rund 250 Programmzeilen von
öffne1 bzw. öffne2b stehen nur jetzt gerade noch 20 Zeilen von öffne3 gegenüber:
import java.io.*; import javax.xml.*; import javax.xml.bind.*; public class Turing_XML_Speichertest3 // Demo: Speichern und Öffnen einer Turingmaschine als Java-Objekt. r-krell.de, 9/2019 { String meldung = ""; // für Warnungen und Fehler beim Speichern und/oder Öffnen public Turing_XML_Speichertest3() // Konstruktor mit Anweisungen für einen Testlauf { Turing_zum_Speichern3 tm0 = beispiel_bitaddition_kurz3(); zeige3 (tm0); speichere3 (tm0, "turi$3$$.xml"); // schreibt auf den Datenträger System.out.println("###### öffne3 #####"); Turing_zum_Speichern3 tm3 = öffne3 ("turi$3$$.xml"); // liest vom Datenträger System.out.println("meldung = ["+meldung+"]"); zeige3 (tm3); } public Turing_zum_Speichern3 beispiel_bitaddition_kurz3() // erzeugt Beispiel-Turingmaschine (wie auf Webseite abgebildet) { ... } public void zeige3 (Turing_zum_Speichern3 tm) // zeigt Daten der Turingmaschine tm im Konsolenfenster an { ... } // ------------------------------------------------------------------------------ // ------ XML-Konzept 3: Schreiben und Lesen mit JAXB-Mitteln ------------------- public void speichere3 (Turing_zum_Speichern3 tm, String datname) // schreibt tm per JAXB-Befehlen als XML auf Datenträger { meldung = ""; // nimmt Warnungen oder Fehlermeldungen beim Schreiben/Speichern auf try { FileOutputStream aufDisk = new FileOutputStream (datname); JAXBContext jc = JAXBContext.newInstance(Turing_zum_Speichern3.class); Marshaller m = jc.createMarshaller(); m.setProperty (Marshaller.JAXB_FORMATTED_OUTPUT, true); m.marshal (tm, aufDisk); aufDisk.close(); } catch (Exception e) { meldung = meldung + "** Fehler beim Schreiben/Speichern der XML-Datei 3 '"+datname+"': **\n ["+e+"]\n"; System.out.println (meldung); } } public Turing_zum_Speichern3 öffne3 (String datname) // gibt vom Datenträger gelesene Turingmaschine als Funktionswert zurück // liest XML-Datei mit JAXB-Mitteln ein und erzeugt tm { meldung = ""; // nimmt Warnungen oder Fehlermeldungen beim Lesen oder Parsen auf Turing_zum_Speichern3 tm = null; // dieses Objekt erhält später die Turingmaschine, die .. try // .. dem Inhalt der XML-Datei entsprechend erzeugt wird { FileInputStream vonDisk = new FileInputStream (datname); JAXBContext jc = JAXBContext.newInstance(Turing_zum_Speichern3.class); Unmarshaller u = jc.createUnmarshaller(); tm = (Turing_zum_Speichern3) u.unmarshal (vonDisk); vonDisk.close(); } catch (Exception e) { meldung = meldung + "** Fehler beim Öffnen/Lesen der XML-Datei 3 '"+datname+"': **\n ["+e+"]\n"; System.out.println (meldung); } return(tm); } } |
Wie der Programmlauf zeigt, wird aus der XML-Datei auch wieder die richtige Turingmaschine rekonstruiert. Allerdings sieht die automatisch erzeugte XML-Datei turi$3$$.xml etwas anders aus als die bisherige Version:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<turingmaschine>
<bandalphabet>
<zeichen>95</zeichen>
<zeichen>48</zeichen>
<zeichen>49</zeichen>
</bandalphabet>
<zustaende>
<zustand>1</zustand>
<zustand>2</zustand>
<zustand>3</zustand>
<zustand>4</zustand>
<zustand>5</zustand>
</zustaende>
<kopfbewegung>
<symbol>60</symbol>
<symbol>61</symbol>
<symbol>62</symbol>
</kopfbewegung>
<maschinentafel>
<eintrag3>
<item>
<zeichen>95</zeichen>
<bewegung>0</bewegung>
<zustand>1</zustand>
</item>
<item>
<zeichen>95</zeichen>
<bewegung>1</bewegung>
<zustand>2</zustand>
</item>
<item>
<zeichen>95</zeichen>
<bewegung>1</bewegung>
<zustand>3</zustand>
</item>
<item>
<zeichen>48</zeichen>
<bewegung>1</bewegung>
<zustand>5</zustand>
</item>
</eintrag3>
<eintrag3>
<item>
<zeichen>95</zeichen>
<bewegung>1</bewegung>
<zustand>2</zustand>
</item>
<item>
<zeichen>48</zeichen>
<bewegung>1</bewegung>
<zustand>5</zustand>
</item>
<item>
<zeichen>49</zeichen>
<bewegung>1</bewegung>
<zustand>5</zustand>
</item>
<item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</eintrag3>
<eintrag3>
<item>
<zeichen>95</zeichen>
<bewegung>1</bewegung>
<zustand>3</zustand>
</item>
<item>
<zeichen>49</zeichen>
<bewegung>1</bewegung>
<zustand>5</zustand>
</item>
<item>
<zeichen>49</zeichen>
<bewegung>1</bewegung>
<zustand>4</zustand>
</item>
<item xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</eintrag3>
</maschinentafel>
<option>0</option>
<kommentar>
<zeile>Diese Turingmaschine addiert zwei Bits.</zeile>
<zeile>Sie ist am Ende des Abschnitts b2) auf www.r-krell.de/if-t-turi.htm als zweite,
kürzere Variante</zeile>
<zeile>beschrieben und wird hier als willkürlich ausgesuchtes Beispiel
verwendet.</zeile>
</kommentar>
<gespeicherteBaender>
<band>
<schritte>0</schritte>
<startposition>2</startposition>
<kopfposition>2</kopfposition>
<inschrift>1_1</inschrift>
<zeile>Die
Summe 1+1=10 soll die beiden Summanden ersetzen</zeile>
</band>
</gespeicherteBaender>
</turingmaschine>
Beim Betrachten der vorstehenden XML-Datei fällt auf:
Natürlich kann Abhilfe geschaffen werden: Ändert man in Turing_zum_Speichern3 die
char[]-Reihungen in Strings und speichert intern alle einzelnen Zeichen statt als char lieber als String der
Länge 1, so erhält man in der neuen, daraus vom Marshaller erzeugten XML-Datei wieder lesbare Buchstaben (wobei Zeichen wie <
automatisch in < verwandelt werden) sowie kompakte Darstellungen von Bandalphabet und Kopfbewegung. Möchte man auch die
Maschinentafel ähnlich darstellen lassen wie in der XML-Datei am Ende des obigen Abschnitts Ca), so bietet sich an, die Klasse
Turing_Eintrag3 um Datenfelder für das alte Zeichen und den alten Zustand zu ergänzen. Für eine angenehme XML-Darstellung
wäre eine dynamische, 1-dimensionale Anordnung der ergänzten Einträge innerhalb der Klasse nötig - am besten eine
ArrayList<Turing_Eintrag3X>. Damit beim Betrieb der Turingmaschine der jeweils passende Eintrag schnell gefunden wird, ist aber eine
2-dimensionale statische Tabelle besser. Die 2-dim. Tabelle bleibt also in der Klasse Turing_zum_Speichern3, erhält aber keine
Annotation (so dass sie nicht in die XML-Datei kommt). Vielmehr werden vorm Speichern ihre Einträge in die zusätzliche Eintragsliste (mit
Annotation) umgefüllt und nach dem Öffnen werden die Einträge aus der Liste an die passenden Stellen der Tabelle gesetzt.
Und auch das Kommentarproblem lässt sich einigermaßen elegant lösen [eine bisher vermisste Annotation der Art
@XMLComment(content="...") wäre natürlich noch besser, ist aber noch nicht in Sicht]: Eine passende String-Konstante als
Datenfeld in Turing_zum_Speichern3 könnte etwa als "<hinweis>Gespeicherte Turingmaschine für das
Programm "TuringMa_J", www.r-krell.de/if-t-turi.htm</hinweis>" in der
XML-Datei erscheinen.
Die hier angesprochenen Verbesserungen werden übrigens - gemeinsam mit weiteren Neuerungen für den Betrieb der Turingmaschine - auf der
nächsten Seite if-t-turi3.htm verwirklicht!
Hinweis: Auch wenn das für dieses Projekt irrelevant ist, sei ergänzend angemerkt, dass der JAXB-Unmarshaller nicht nur wie in öffne3 ein Java-Objekt, sondern mit ähnlich geringem Aufwand wohl auch einen DOM-Baum füllen könnte (s.o.). Und beim Marshalling kann anscheinend nicht nur eine XML-, sondern auch eine (hier nicht benötigte und daher auch nicht besprochene) Schema-Datei erzeugt werden. (Wer XSD-Schemadateien braucht, sei auch auf das in der Java-Entwicklungsumgebung enthaltene Zusatzprogramm xjc.exe hingewiesen).
zum Seitenanfang / zum Seitenende
Das Speichern und Öffnen in bzw. von XML-Dateien ist sicherlich der zukunftssicherste und sinnvollste Weg - besser als die zuvor in den Kapiteln A und B vorgestellten Alternativen zum Speichern der Turingmaschine als Java-Objekt oder in einer relationalen MySQL-Datenbank.
Die in Cb1) und Cb2) dargestellten Verfahren für das Speichern der Turingmaschine in einer (und das Lesen/Öffnen aus einer) XML-Datei schrecken allerdings durch den hohen Programmieraufwand ab. Auch wenn sich mit der JAXB-Bibliothek in Cb3) nicht genau die anfangs naiv geplante XML-Datei darstellen lässt, kann mit den zuletzt zur Abhilfe vorgeschlagenen Anpassungen der Eintrags- und der Turingmaschinen-Klasse der unverändert kurze Programmtext von speichere3 und öffne3 mit einem auch für Menschen gut lesbaren Aussehen der dann automatisch erzeugten XML-Datei verbunden werden. Das Gesamtprojekt muss unter den Anpassungen nicht leiden. Deshalb ist diese Art des Speicherns und Öffnens die beste Empfehlung für das Projekt und wird im Folgenden auch benutzt.
Bei der Verwendung der XML-Bibliotheken führen übrigens Umlaute in den Tag-Bezeichnern oft zu Problemen [die Cb1) nicht hat]. Auch Fremdprogramme haben oft mit Umlauten Schwierigkeiten, sodass im Gegensatz zur in Ca) abgebildeten XML-Datei in Cb) immer mit dem Tag-Paar <zustaende> .. </zustaende> (statt ursprünglich <zustände> .. </zustände>) gearbeitet wurde.
zum Seitenanfang / zum Seitenende
zurück zur Startseite des "Turingmaschinen-Projekts": allgemeine Theorie der Turingmaschinen, Projektvorhaben
weiter zur nächsten Seite des "Turingmaschinen-Projekts": Benötigte Eigenschaften und Fähigkeiten im
Hintergrund
zurück zum Anfang der Informatik-Hauptseite
direkt zur Übersicht aller "Informatik-mit-Java"-Seiten auf der Informatik-Hauptseite