1. Einführung
Es besteht die Möglichkeit, dass Sie nach der Lektüre dieses Artikels zu dem Schluss kommen, dass niemand, der bei klarem Verstand ist, jemals das verwenden würde, was in diesem Artikel vorgeschlagen wird. Lassen Sie mich daher mit einem Haftungsausschluss beginnen: Ich habe nie öffentlich Behauptungen über meinen Geisteszustand aufgestellt. Abgesehen davon denke ich, dass ein Artikel über eine Technologie, die fast niemand verwendet, immer noch viel interessanter als ein Artikel den jeder benutzt. Je älter Sie sind und je mehr Dinge Sie schon gesehen haben, desto unwahrscheinlicher ist es, dass Sie sich für etwas interessieren, das schon viele Male zuvor gemacht wurde. Man könnte also auch sagen, dass die erfahrensten Leute sich wahrscheinlich für Dinge interessieren, die noch niemand benutzt. Dieser Artikel richtet sich an diese Leute. Damit ist das Thema erledigt: Pecia ist eine neue Methode zur Erstellung von Dokumentationen aus Ihren Java-Anwendungen. Sie werden sich wahrscheinlich fragen, warum wir noch eine weitere Möglichkeit zur Generierung von Dokumenten aus Java benötigen, und ich muss zugeben, dass die Java-Welt nicht schlecht dasteht, wenn es um die Anzahl der Frameworks geht, mit denen Sie Dokumente generieren können. Aber Pecia versucht es noch einmal, und ich musste einfach sehen, ob es funktionieren würde. Beurteilen Sie selbst, ob es Sinn macht.
2. Hintergrund
Ich erinnere mich noch genau an den Tag, an dem ich mehr Zeit damit verbracht habe, herauszufinden, warum mein Maven-Bericht kein wohlgeformtes HTML erzeugt hat, als ich zugeben möchte. Definitiv nicht einer meiner besten Momente.
Maven Berichte werden mit einer API namens Doxia erstellt. Es handelt sich dabei nicht um einen allgemeinen, auf Vorlagen basierenden Textgenerierungsmechanismus wie Velocity, FreeMarker oder StringTemplate. Tatsächlich gibt es überhaupt keine Vorlage. Stattdessen bietet Doxia eine API für die Erstellung eines Dokuments, die die endgültige Darstellung dieses Dokuments abstrahiert.
In gewisser Weise ist die wichtigste Schnittstelle in Doxia die Sink-Schnittstelle. Die
Beispiel 1. Verwendung der Doxia Spüle
[java]
sink.body();
sink.sectionTitle1();
sink.text("Nachrichtenkatalog");
sink.sectionTitle1();
sink.table();
sink.tableRow();
sink.tableHeaderCell();
sink.text("Typ");
sink.tableHeaderCell();
sink.tableHeaderCell();
sink.text("Kennung");
sink.tableHeaderCell();
sink.tableHeaderCell();
sink.text("Nachricht");
sink.tableHeaderCell();
sink.tableRow_();
...
[/java]
Unverkennbar gibt es eine Entsprechung zwischen der Sink-Schnittstelle und einer Teilmenge des HTML-Inhaltsmodells. HTML ist jedoch nicht die einzige Art von Dokument, die von Doxia erzeugt werden kann. Durch die Abstraktion der Schnittstelle von der Implementierung ist Doxia in der Lage, jede Art von Ausgabedokument zu erzeugen. Daher ist die Generierung von PDF genauso einfach wie die von HTML.
Der Ansatz von Doxia hat jedoch eine Einschränkung: Sie können nie wirklich sicher sein, ob Ihr Code ein gültiges Dokument erzeugt. Die API hindert Sie nicht daran, ein Bild in eine Tabellenzeile einzufügen oder Text in eine Tabellenzeile außerhalb einer Tabellenzelle einzufügen. Und da Doxia darauf abzielt, Sie von der Zieldarstellung zu abstrahieren, ist es ziemlich schwierig, Annahmen darüber zu treffen, was als gültig gilt und was nicht.
Das war der Grund, warum ich so viel Zeit mit der Fertigstellung meines Maven-Berichts verbracht habe. Es stellte sich heraus, dass ich den falschen Typ von Dokumentenelementen zur falschen Zeit 'gebaut' hatte. Die API hat mich daran nicht gehindert, und das Framework hat mich zur Laufzeit nicht gewarnt. Was mich zum Nachdenken brachte....
3. Pecia
Ich habe mich gefragt, ob es nicht möglich wäre, die Validierung zur Kompilierungszeit zu erzwingen. Wäre es nicht viel schöner, wenn die API verhindern würde, dass ich Bilder zu Tabellenzeilen hinzufüge oder - zum Beispiel - eine vierte Tabellenzelle zu einer dreispaltigen Tabelle hinzufüge? Wäre es nicht viel schöner, wenn die API verhindern würde, dass ich irgendwelche Fehler wie diese zu machen? Und wäre es nicht toll, wenn die API eine fließende API wäre[]? Aus meiner Sicht lautet die Antwort auf all diese Fragen: Ja, das wäre viel schöner. Es würde mir ermöglichen, Fehler schnell zu erkennen, und außerdem würde die Code-Vervollständigungshilfe meiner IDE dadurch tatsächlich wertvoll werden. Mit der Sink-Schnittstelle von Doxia bietet Ihnen Ihre IDE die Möglichkeit, Bilder zu einer Tabellenzeile hinzuzufügen. In einem Framework, das über seine API eine ordnungsgemäße Dokumentstruktur erzwingt, würde Ihre IDE dies niemals als praktikable Option betrachten.
4. Pecia API-Grundsätze
4.1. Kontextbasiert
Die von Pecia angebotene API hängt von Ihrem Kontext ab. Das lässt sich wahrscheinlich am besten anhand eines Beispiels erklären.
Beispiel 2. Verwendung der Pecia API
Artikel Artikel = ...;
ItemizedList list = article.itemizedList();
list.item("erster Eintrag");
list.item("zweites Element");
Was passiert also in dem obigen Beispiel? Nun, zunächst erstellen Sie das Zieldokument. Dazu später mehr. Von diesem Zeitpunkt an können Sie dem Artikel Inhalte hinzufügen. Sobald Sie eine Artikelliste hinzufügen, gibt die API eine ItemizedList-Instanz zurück, eine Objektdarstellung dieses neuen Kontexts. Wenn Sie der Artikelliste Inhalte hinzufügen möchten, müssen Sie Operationen für die ItemizedList-Instanz aufrufen. In diesem Fall fügt das Beispiel der ItemizedList zwei Einträge hinzu.
Genau wie Doxia wird auch Pecia von einer Reihe von Implementierungen der API unterstützt. Zum jetzigen Zeitpunkt unterstützt es sowohl die DocBook- als auch die HTML-Ausgabe. Mit dem obigen Beispielcode würde eine einfache HTML-Implementierung eine HTML-Ausgabe wie diese erzeugen:
<html>
<Körper>
<ul>
<li>erster Punkt</li>
<li>zweiter Punkt</li>
</ul>
</body>
</html>
4.2. Methodenverkettung
Nun, das oben genannte Beispiel ist nicht wirklich illustrativ für die Art und Weise, wie Sie Code mit Pecia schreiben würden. Mit der Methodenverkettung können Sie das Dokument erstellen, ohne Variablen für jedes dazwischen liegende Inhaltsmodellelement zu deklarieren, und eine Schnittstelle schaffen, die eine größere Ähnlichkeit mit der Art und Weise aufweist, wie Sie normalerweise Dokumente in Auszeichnungssprachen wie HTML oder DocBook erstellen würden. Sagen wir einfach, eine flüssigere Schnittstelle. Anstelle des Codes aus dem vorigen Kapitel können Sie also einen Code wie den folgenden schreiben:
Beispiel 3. Verkettung von Methoden
Artikel Artikel = ...;
artikel.itemizedList()
.item("erster Artikel")
.item("zweiter Artikel");
4.3. Stenografische Notationen
Dies ist ein weiteres Musterdokument:
Beispiel 4. Gemischte 'erweiterte' und Kurzschrift-Notationen
Artikel
.author("Wilfred Springer")
.copyright("agilejava.com", 2008)
.para()
.text("Dies ist die ")
.emphasis("erste")
.text(" Absatz.")
.end()
.para("Und dies ist die zweite.")
.end()
Dadurch wird etwas Ähnliches wie das hier erzeugt:
<html>
<Körper>
<p>Dies ist die <em>erste</em> Absatz.</p>
<p>Und dies ist die zweite.</p>
</body>
</html>
Das wichtige Prinzip, das hier veranschaulicht wird, ist, dass Pecia sowohl über Kurzschrift-Notationen als auch über ausführlichere Notationen für die Angabe von Inhalten verfügt. Die einfache Operation para(String) (dargestellt durch den zweiten Absatz im Beispiel) beginnt den Absatz, fügt Text hinzu und schließt den Absatz. Sie lässt sich also im Grunde wie folgt erweitern:
.para()
.text("Und das ist die zweite.")
.end()
Das Prinzip gilt nicht nur für Absätze. Es gilt auch für andere Dokumentelemente, wie Listenelemente, Tabellenzellen und Fußnoten. In all diesen Fällen können Sie das Dokumentenelement mit einer einfachen Operation hinzufügen, die einen String mit dem Text akzeptiert, der in das Dokumentenelement eingebettet werden soll, oder durch den Aufruf einer Operation ohne aufrufen, die den Kontext in den Kontext des Dokumentenelements ändert. Nehmen wir ein API-Snippet als Beispiel. Beispiel 5, "Pecia API Snippet" zeigt die Signatur einiger Operationen auf Para, der von Paragraphen implementierten Schnittstelle. Wie Sie sehen können, gibt es zwei verschiedene Fußnotenoperationen. Sie unterscheiden sich in mehrfacher Hinsicht. Zunächst einmal benötigt die erste Operation ein String-Argument, die zweite nicht. Die erste Operation erstellt eine Fußnote, fügt einen Absatz hinzu und fügt dem Absatz in einem einzigen Aufruf Text hinzu. Sobald dies geschehen ist, gilt die gesamte Fußnote als fertiggestellt. Der Kontext ist nicht mehr die Fußnote, die gerade dem Absatz hinzugefügt wurde. Der Kontext ist - wieder - der Absatz selbst.
Beispiel 5. Pecia API Schnipsel
Schnittstelle Para<T> {
...
Para<T> Fußnote(String text);
Fußnote<? erweitert Para<T>> Fußnote();
...
}
Der andere Fußnotenvorgang ist nicht ein String-Argument. Die API geht davon aus, dass Sie nicht daran interessiert sind, eine leere Fußnote hinzuzufügen (warum auch?), und ändert den aktuellen Kontext in den Kontext der Fußnote. Von diesem Zeitpunkt an können Sie nur noch Operationen aufrufen, die durch die Fußnotenschnittstelle definiert sind, bis Sie schließlich der Meinung sind, dass Sie mit der Fußnote fertig sind und ihre end()-Operation aufrufen, wodurch der ursprüngliche Kontext wiederhergestellt wird.
4.4. Tische
Tabellen verdienen eine besondere Aufmerksamkeit. Um eine gültige Dokumentstruktur zu erhalten, wollen Sie nicht nur Tabellenzellen auf Tabellenzeilen beschränken; Sie müssen auch sicherstellen, dass jede Zeile genau genau Die Durchsetzung dieser Eigenschaft von Tabellen hat sich als schwierig erwiesen. Bevor wir ins Detail gehen, wollen wir uns zunächst ein Beispiel ansehen:
Beispiel 6. Eine Tabelle in Pecia
artikel.table2Cols()
.header()
.entry().para("col1")
.entry().para("col2")
.end()
.row()
.entry().para("foo")
.entry().para("bar")
.end()
.row()
.entry().para("foo")
.entry().para("bar")
.end()
.end();
Beispiel 6, "Eine Tabelle in Pecia" zeigt, wie Sie eine Tabelle erstellen, die mehr oder weniger dieser HTML-Tabelle entspricht:
<Tabelle>
<tr>
<th><p>col1</p></th>
<th><p>col2</p></th>
</tr>
<tr>
<td><p>foo</p></td>
<td><p>Bar</p></td>
</tr>
<tr>
<td><p>foo</p></td>
<td><p>Bar</p></td>
</tr>
</table>
Was genau passiert also in Beispiel 6, "Eine Tabelle in Pecia"? Nun, zuerst konstruiert die Operation table2Cols() eine Tabelle mit zwei Spalten. Das erstellte Objekt lässt nur Operationen für Tabellen mit zwei Spalten zu. Danach fügen wir als erstes einen Tabellenkopf hinzu, indem wir header() für die Tabelle aufrufen. Da es sich um eine zweispaltige Tabelle handelt, kann die Kopfzeile nur zwei Zellen enthalten. Jeder Versuch, mehr oder weniger als diese zwei Zellen hinzuzufügen, führt zu Kompilierungsfehlern. Jede Tabellenzelle wird durch den Aufruf von entry() erstellt. Der resultierende Kontext ist eine Tabellenzelle. Es gibt eine Reihe von Dingen, die Sie einer Tabellenzelle hinzufügen können, wie z.B. Absätze. Sobald Sie mit der Zelle fertig sind, rufen Sie entweder entry() oder end() auf. Wenn Sie entry() aufrufen, wird die nächste Tabellenzelle erstellt. Ein Aufruf von end() markiert das Ende der aktuellen Tabellenüberschrift. Und aufgrund der Art und Weise, wie Pecia aufgebaut ist, können Sie end() nur nach der letzten Tabellenzelle und entry() nur vor der letzten Tabellenzelle aufrufen. Tabellenköpfe werden in genau wie Tabellenzeilen hinzugefügt, nur dass Sie in diesem Fall row() statt header() aufrufen.
4.5. Metadaten
Einige Dokumentelemente können mit Metadaten verknüpft sein. Dabei handelt es sich oft um Daten, die nicht unbedingt Teil des Hauptdokumentenflusses sind. In solchen Fällen können Sie in Pecia Metadaten am Anfang des Dokumentenelements angeben, zu dem sie gehören. Nehmen wir als Beispiel einen Artikel. Ein Artikel kann einen Autor haben. Am Anfang eines Artikels, bevor Sie dem Artikel Inhalte hinzufügen, können Sie Metadaten wie den Namen des Autors hinzufügen. Sobald Sie mit dem Hinzufügen von Inhalten zum Artikel begonnen haben, ist es nicht mehr möglich, weitere Metadaten hinzuzufügen. Beispiel 7, "Artikel-Metadaten", zeigt Ihnen eine gültige Methode, diese zu verwenden. Beispiel 8, "Unzulässige Artikel-Metadaten" veranschaulicht eine ungültige Art der Angabe von Metadaten; der Compiler wird nicht akzeptiert keine weiteren Metadaten nachdem Inhalt hinzugefügt worden ist.
Beispiel 7. Artikel-Metadaten
Artikel
.author()
.vorname("Wilfred")
.nachname("Springer")
.end()
.para("Dies ist der erste Absatz.");
Beispiel 8. Unzulässige Artikel-Metadaten
Artikel
.para("Dies ist der erste Absatz.")
.author()
.firstname("Wilfred")
.surname("Springer")
.end()
5. Pecia verwenden
Im vorigen Abschnitt haben Sie die meisten der grundlegenden Prinzipien von Pecia kennengelernt. Sie haben jedoch nicht wirklich gesehen, wie Sie sicherstellen, dass als Ergebnis ein Ausgabedokument erzeugt wird. Das wurde absichtlich so gemacht. Das Wichtigste ist hier die oben beschriebene API. Wie Sie eine tatsächliche Implementierung in die Hände bekommen und wie diese Implementierung mit den Dokumenten umgeht, die Sie erstellen, ist implementierungsspezifisch. Glücklicherweise ist Pecia tatsächlich mit einer Implementierung geliefert. Und so verwenden Sie die Implementierung:
Beispiel 9. HTML erzeugen
// Die Standardimplementierung verwendet einen Wrapper um STaX herum, um
// XML-Dokumente erzeugen.
XmlWriter writer = new StreamingXmlWriter(...);
// Der DocumentBuilder wird die Ausgabe tatsächlich produzieren.
DocumentBuilder builder = new HtmlDocumentBuilder(writer);
// Aber wenn wir Dokumente erstellen, brauchen wir eine
// Implementierung der oben genannten Schnittstellen. Wickeln wir
// der DocumentBuilder in einer Artikel-Implementierung. (Die
// Das zweite Argument ist der Titel des Artikels.)
ArticleDocument document =
new DefaultArticleDocument(builder, "Beispiel");
// ... und jetzt können wir das Dokument erstellen.
Dokument
.section("Erster Abschnitt")
.section("Erster Unterabschnitt")
.end()
.end()
.end();
Die Standardimplementierung von Pecia erzeugt die Ausgabe im laufenden Betrieb. Technisch gesehen hindert Sie nichts daran, das gesamte Dokument zunächst im Speicher zu erstellen und anschließend die Ausgabe zu generieren. All dies ist also nur eine Implementierung. Ich vermute sogar, dass die Implementierung irgendwann in der Zukunft erheblich geändert wird; die Verwendung dieser Implementierung erfolgt auf eigene Gefahr.
6. Staat Pecia
Nachdem Sie den vorherigen Abschnitt gelesen haben, ahnen Sie wahrscheinlich schon, dass Pecia noch nicht fertig ist. Es ist brauchbar, und es ist tatsächlich in einem meiner Projekte im Einsatz, aber es gibt noch einiges zu tun. Dieser Artikel behandelt also gewissermaßen eine Alpha-Version der API.
7. Pecia Document Object Model
Das heute von Pecia unterstützte Dokumentenobjektmodell ist recht einfach. In der Tat ist es wahrscheinlich viel zu einfach. Das ist ein weiterer Grund, warum es noch keine Version 1.0 von Pecia gibt.
Abbildung 1. Pecia Dokument-Objektmodell
Abbildung 1, "Pecia Document Object Model" bietet einen schematischen Überblick über das unterstützte Dokumentobjektmodell. Die Pfeile kennzeichnen mögliche Beziehungen zwischen den Elementen: ein Listenelement kann Absätze enthalten, Abschnitte können Tabellen, Listen, wörtliche Inhalte und andere Abschnitte enthalten usw. Die unterstützten Dokumentenelemente sind ziemlich selbsterklärend. Die einzige Ausnahme könnte xref sein, das einen internen Verweis auf einen anderen Teil des Dokuments darstellt.
8. Zusammenfassung & Schlussfolgerungen
In diesem Artikel habe ich versucht, die Schaffung eines weiteren Frameworks für die Erstellung von Dokumentation zu rechtfertigen. Es entstand aus der Unzufriedenheit mit den bestehenden Lösungen und hatte dann einige interessante Nebeneffekte. Pecia verhindert nicht nur, dass Sie die Dokumentstruktur in den Dokumenten, die Sie zur Kompilierzeit erzeugen, verletzen, sondern unterstützt auch die IDE dabei, diese Fehler bei der Programmierung ganz zu vermeiden. Es gibt sogar noch weitere Vorteile, die ich noch gar nicht erwähnt habe. Sie sind vielleicht weniger greifbar, aber dennoch sehr real. Ich habe diesen Vorteil erkannt, als ich Pecia in einem Projekt einsetzte, in dem ich 40 kleine Objekte im Umlauf hatte, von denen jedes je nach Kontext unterschiedlich in meinem Dokument dargestellt werden musste. In solchen Situationen zwingen Sie viele der vorhandenen Frameworks, die üblicherweise für die Erstellung von Dokumenten verwendet werden, dazu, dass alle diese 40 Objekte ihren Zustand nach außen hin offenlegen müssen, damit sie von einer externen Vorlage aus an sie gebunden werden können. Dies verstößt jedoch gegen eines der wichtigsten Prinzipien der Objektorientierung: das Prinzip der Kapselung. Dadurch, dass die Objekte ihren gesamten Zustand der Außenwelt preisgeben, haben Sie die Abhängigkeiten zwischen der Außenwelt und der Implementierung des Objekts erhöht. Anstatt das Verhalten als Teil des Objekts zu definieren, wird das Verhalten (die Art und Weise, wie es dargestellt wird) externalisiert vollständig. Infolgedessen wird die Pflege der Vorlagen zu einem Albtraum; Ihre Vorlagen müssen ein tiefes Verständnis für die Interna aller Objekte haben. Doxia würde es Ihnen bereits ermöglichen, einen anderen Ansatz zu wählen: Sie könnten möglicherweise eine gemeinsame Schnittstelle für alle Objekte definieren, die es jedem Objekt ermöglicht, sich selbst über die Sink-Schnittstelle darzustellen. Aber wie würden Sie den Kontext vermitteln, in dem der Inhalt geschrieben werden muss? Wie würden Sie sicherstellen, dass Ihr Objekt versteht, dass es sich selbst als Teil eines Absatzes darstellen muss. Oder als Tabelle? Wie stellen Sie sicher, dass das Objekt nicht außerhalb des Kontexts schreibt, den das aufrufende Programm erwartet?
void render(Sink sink) {
// Oh, woher soll ich wissen, ob ich mich in einem Absatz- oder Tabellenkontext befinde?
}
In Doxia gibt es keine Möglichkeit, dieses Problem zu lösen. In Pecia ist dies trivial. Die gemeinsame Schnittstelle würde einfach eine einzige Operation für jeden Kontext definieren, in dem das Objekt sich selbst darstellen muss:
void render(Para<?> para) {
// ah, ich muss es als Teil eines Absatzes darstellen
// Ende gibt es keine Möglichkeit, außerhalb dieses Kontexts zu schreiben.
}
Ich will damit nur sagen: Es scheint ein Argument für Frameworks wie Pecia zu geben. Zugegeben, die aktuelle Version von Pecia ist noch recht jung, und es gibt sicherlich Dinge, die sich zu gegebener Zeit ändern werden, aber ich bin zu der Überzeugung gelangt, dass es Potenzial hat, und ich hoffe, dass ich auch Sie davon überzeugen konnte. Pecia wird derzeit bei SourceForge gehostet. Siehe https://www.martinfowler.com/bliki/FluentInterface.html
Verfasst von

Wilfred Springer
Contact



