www.r-krell.de
Webangebot für Schule und Unterricht, Software, Fotovoltaik und mehr

Willkommen/Übersicht  >  Informatik  >   Informatik mit Java, Seite t2)

Informatik mit Java

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



Speichern in unterschiedlichen Darstellungsarten
auf beliebigen Datenträgern



a) Medien-unabhängiges Speichern

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

b) Zu speichernde Informationen und drei verschiedene Konzepte der Datenspeicherung

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

A. Daten als Java-Objekt speichern



Aa) Geeignetes Java-Objekt

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

Ab) Methoden zum Speichern bzw. Öffnen des Java-Objekts

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:

Konsolenansicht mit den beiden Kontrollausdrucken durch den Aufruf von zeige im Java-Programm

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

Ac) Kontrolle der auf dem Datenträger erzeugten Datei und Beurteilung des 1-Objekt-Verfahrens

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:

Bildschirmabdruck mit dem Hex-Dump der Datei 'turi$$$.tma'

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

B. Daten in einer Datenbank speichern



Ba) Geeigneter Datenbankentwurf

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:

handschriftliche Übersicht über die 7 sinnvollen Tabellen für die Beipiel-Turingmaschine

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 (022"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

Bb) Methoden zum Speichern in (und Lesen aus) einer Datenbank

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.

Bildschirmabdruck vom MySQL-Querybrowser mit SQL-Befehlen
Das in Java zu schreibende Simulationsprogramm muss genau diese SQL-Befehle an den Datenbank-Server übermitteln, wenn die Beispiel-Turingmaschine zur Addition zweier Bits in einer Datenbank gespeichert werden soll. Das Java-Programm kommuniziert dabei nur als Client mit dem Datenbank-Server, der zusätzlich zum Java-Programm (genauso wie hier zusätzlich zum Querybrowser) die ganze Zeit im Hintergrund bzw. per Netzwerk erreichbar laufen muss. Beim vorstehenden Test wurde ein lokaler MySQL-Server benutzt, der mit dem XAMPP Control Panel gestartet worden war, wie das nach vorne geholte Kontroll-Fenster zeigt.

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

Bc) Beurteilung des Datenbank-Verfahrens

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

C. Daten in einer Textdatei speichern



Ca) Geeignete Textformen inkl. XML

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:

Bildschirmfoto Notepad mit Version 1 einer Textdarstellung
In der Textversion 1 wurden extra Leerzeichen zwischen die Zeichen des Bandalphabets oder die Symbole der Kopfbewegungen, zwischen die Zustandsnamen oder zwischen die Komponenten der Maschinentafel-Einträge eingefügt, um verschiedene Werte zu trennen. Trotzdem lässt sich diese erste Version schlecht wieder einlesen, insbesondere wenn Zustandsnamen auch aus mehreren Zeichen einschließlich des Leerzeichens bestehen dürfen: Wie wäre z.B. eine zweite Zeile "Start Z 1 Z 2 Zus 3 Halte-Z" zu interpretieren? Und auch die Maschinentafel-Einträge würden unübersichtlich, zumal bei einer Kopfbewegung nach links die Bewegung durch die zweistellige -1 dargestellt würde.

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.

Bildschirmfoto Notepad mit Version 2 einer Textdarstellung (CSV mit Komma und Semikolon)
Aus dieser Darstellung lässt sich selbst mit Zustandsnamen wie "Z 2" oder Kopfbewegungen wie "-1" von einem geeigneten Programm wieder die ursprüngliche Maschine rekonstruieren. Falls in Zustandsnamen oder in Kommentaren das Anführungszeichen selbst vorkommt, müsste es allerdings z.B. entweder verdoppelt oder mit einem Schrägstrich \ gekennzeichnet werden, um nicht als Endmarkierung des Wertes zu gelten. Der Parser muss also """" oder "\"" als genau ein gewolltes Anführungszeichen erkennen. Zeilenumbrüche und nicht von Anführungszeichen umschlossene Leerzeichen werden als bedeutungslos ignoriert. Neben dem Komma ist hier das Semikolon wichtig, weil sonst die Anzahl der Zustände oder das Ende des aus unterschiedlich vielen Zeilen bestehenden Kommentars nicht ersichtlich wird.

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:

Bildschirmfoto Editpad++ mit dem Text in Version 3 im XML-Format

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 &lt; und &gt; (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



Cb) Methoden zum Speichern und Öffnen eines Textes im XML-Format



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.

Cb1) Speichern (Schreiben) und Öffnen (Lesen) einer XML-Datei für die Turingmaschine
mit herkömmlichen Java-Befehlen für Strings und normale Textdateien

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 &lt; und &gt; 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 &amp;) 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 = "&lt;";  break;  
        
case '>' : s = "&gt;";  break
        
case '&' : s = "&amp;"break// weil & sonst Beginn von &gt; oder &lt;
      } 
      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: &gt;, &lt; und &amp; 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

Cb2) Speichern (Schreiben) und Öffnen (Lesen) einer XML-Datei für die Turingmaschine
mit Java-Befehlen aus der mitgelieferten StAX-Bibliothek

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 &lt;, &gt;, und &amp;
    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 &gt; 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

Cb3) Speichern (Schreiben) und Öffnen (Lesen) einer XML-Datei für die Turingmaschine
mit Java-Befehlen aus der mitgelieferten JAXB-Bibliothek

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 &lt; 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

Cc) Beurteilung der XML-Verwendung

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


zum Anfang dieser Seite
Willkommen/Übersicht  -  Was ist neu?  -  Software  -  Mathematik  -  Physik  -  Informatik  -   Schule: Lessing-Gymnasium und -Berufskolleg  -  Fotovoltaik  -  & mehr  -  Kontakt: e-Mail, Impressum  -  Grußkarten, site map, Download und Suche
Diese Seite ist Teil des Webangebots http://www.r-krell.de