Die Reader API

Wer spricht schon absichtlich gebrochen Spanisch, obwohl er die Sprache fließend beherrscht? In gleicher Weise könnte man Fragen, wer eine komplexere Non-Fluent API benutzen will, wenn es eine »fließende« Variante gibt. Der Vergleich hinkt jedoch: Wer eine Sprache nur gebrochen spricht, beherrscht sie nicht vollständig und kann weniger darin ausdrücken. Für die Reader API trifft das Gegenteil zu: Sie bildet die architektonische Basis der im letzten Blog-Eintrag vorgestellten Fluent Reader API, bietet insgesamt eine größere Mächtigkeit und mehr Konfigurationsvielfalt. Bezahlt werden müssen diese Vorteile mit anspruchsvolleren Methodenaufrufen, die mehr Hintergrundwissen und Eigenarbeit erfordern.

Besonders deutlich wird dies mit der Anforderung des asynchronen Ladens von Dokumenten. Da die Reader API per Definition grundsätzlich synchron arbeitet, bleibt es dem Nutzer der Schnittstelle überlassen, den Ladevorgang in einen Thread zu »verpacken« um so die gewünschte Nebenläufigkeit zu erreichen. Dies resultiert zwar in einem gewissen Mehraufwand, lässt andererseits aber auch sämtliche Möglichkeiten und Einsatzszenarien offen.

Einfaches Laden von Dokumenten

Kernstück der Reader API bildet die Klasse Reader. Das folgende Minimalbeispiel zeigt, wie ein Dokument aus einem gegebenen InputStream eingelesen werden kann. Der Lesevorgang geschieht synchron und nach Verarbeitung der abgedruckten Zeilen steht das Dokument für die Bildschirmanzeige zur Verfügung.

Reader reader = new Reader();
reader.read(getSomeInputStream());// Dokument mit 2 Seiten
Document doc = reader.getDocument();// Dokument mit 2 Seiten

Zusammengesetztes Laden von Dokumenten

jadice Dokumente können jeweils einem physikalischen Dokument direkt entsprechen. Darüber hinaus können sie zusätzlich aus beliebig vielen anderen Dokumentquellen aggregiert werden und stellen in diesem Sinne virtuelle Dokumente dar. Einem virtuellen Dokument können durch weitere Aufrufe der Methode read(InputStream is) zusätzliche Seiten angefügt werden. Dies ist der Standardfall:

Reader reader = new Reader();
reader.read(getSomeInputStream());  // Dokument mit 2 Seiten
reader.read(getAnotherInputStream()); // Dokument mit 3 Seiten
Document doc = reader.getDocument(); // Resultat: jadice Dokument mit 5 Seiten

Die Seiten finden sich deshalb am Ende des Dokuments wieder, weil ein interner Zähler automatisch inkrementiert wird – bei Bedarf kann ein InputStream an jede beliebige Seitenposition geladen werden. Die Position des internen Zählers kann durch einen Aufruf der Methode setTargetIndex(int index) beeinflusst werden. Zu beachten ist, dass die Seitennummerierung mit 0 beginnt. Das obige Beispiel würde dann folgendermaßen aussehen:

Reader reader = new Reader();
reader.setTargetIndex(0); // (unnötig, da initialer Index = 0)
reader.read(getSomeInputStream());  // Dokument mit 2 Seiten
reader.setTargetIndex(2); //  (unnötig, da interner Index = 2)
reader.read(getAnotherInputStream()); // Dokument mit 3 Seiten
Document doc = reader.getDocument(); // Resultat: jadice Dokument mit 5 Seiten

Auch ein Seitenindex, der die aktuelle Seitenanzahl übersteigt, ist möglich. Dabei entstehende »Seitenlücken« werden mit leeren Seitenhülsen ohne grafische Inhalte aufgefüllt. Diese Seitenhülsen können dann zu einem späteren Zeitpunkt mit Dokumentdaten befüllt werden. Seitenhülsen ohne Inhalt führen zu einer leeren Anzeige. Im folgenden Beispiel werden drei Streams geladen und mittels setTargetIndex(int index) verschiedenen Seitenpositionen zugewiesen.

Reader reader = new Reader();
reader.setTargetIndex(0); // (unnötig, da initialer Index = 0)
reader.read(getSomeInputStream());  // Dokument mit 2 Seiten
reader.setTargetIndex(4); //  damit entsteht eine Lücke von 2 leeren Seitenhülsen
reader.read(getAnotherInputStream()); // Dokument mit 3 Seiten
Document doc = reader.getDocument(); // Resultat: jadice Dokument mit 7 Seiten,
// wobei die Seiten mit Index 2 und 3 leere
// Seitenhülsen sind

Ein späteres Befüllen der Seitenhülsen könnte dann wie folgt aussehen:

Reader reader = new Reader();
reader.setDocument(doc); // doc aus obigem Beispiel
reader.setTargetIndex(2); // Index, der bis jetzt eine leere
// Seitenhülse enthält
reader.read(getSomeInputStream());  // Dokument mit 2 Seiten
// Resultat: doc ist jetzt ein jadice Dokument mit 7 Seiten,
// wobei die Seiten mit Index 2 und 3 nun befüllt sind

 

Dokumente in Seitenebenen laden

Der vorhergehende Blog-Eintrag zur Fluent Reader API beschreibt ausführlich, wie mehrere InputStreams in die Ebenen einer Seite zusammengefasst werden können. Die Reader API bietet dazu die Möglichkeit ein Layer-Mapping zu verwenden. Das Layer-Mapping wird definiert über eine Map<DocumentLayer, DocumentLayer>, die auf dem Reader registriert wird und eine 1:1-Zuordnung von Quellebenen zu Zielebenen festlegt. Das Ergebnis des folgenden Beispiels entspricht dem Screenshot aus dem Fluent Reader Blog-Eintrag.

Reader reader = new Reader();
reader.read(getContentInputStream()); // Dokument in Standard Dokument-Ebene laden

reader.setTargetIndex(0);// target index zurücksetzen, um auf eine
// andere Ebene der Seite zu laden
Map<DocumentLayer, DocumentLayer> layerMapping
= new HashMap<DocumentLayer, DocumentLayer>();
reader.setLayerMapping(layerMapping); // ein leeres Layer Mapping erstellen
// und am Reader registrieren

layerMapping.put(DocumentLayer.DEFAULT, DocumentLayer.ANNOTATIONS); // Annotationen in Annotationsebene abbilden
reader.read(getAnnoInputStream()); // Laden der Annotationen

layerMapping.put(DocumentLayer.DEFAULT, DocumentLayer.FORM); // Watermark in FORM-Ebene abbliden
reader.read(getWatermarkInputStream()); // Watermark laden

layerMapping.put(DocumentLayer.DEFAULT, DocumentLayer.BACKGROUND); // Hintergrunddaten in Hintergrundebene abbilden
reader.read(getBackgroundInputStream()); // Hintergrund laden

Document doc = reader.getDocument();

Fazit

Für komplexe Szenarien bietet die Reader API mächtige Möglichkeiten. Neben den beschriebenen Anwendungsfällen stellt sie zudem eine feingranulare Kontrolle über die zu lesenden Ziel-Formate sowie Methoden zur Verarbeitung von Metadaten und Properties zur Verfügung.

Die Mehrzahl aller Anwendungsfälle wird durch die Fluent Reader API komfortabel abgedeckt. Daher ist im Standardfall die leichter bedienbare Fluent API zu empfehlen. Für anspruchsvollere Szenarien bietet die Reader API umfangreiche Lösungsmöglichkeiten