Beispiel 7.10. Synchrones Erzeugen eines FontManager
s
Dieses Beispiel zeigt die Erstellung einer FontManager
Instanz. Die ID ist
ein frei wählbarer eindeutiger String, der zur späteren Identifikation
(beispielsweise in Log-Ausgaben) dient. Die Erzeugung erfolgt synchron auf dem
aktuellen Thread.
String id = "any unique identifier";
FontManagerFuture future = FontManagers.startBuilding(id)
.synchronously()
.withSystemFonts()
.finish();
FontManager fontManager = future.get();
|
Da die Erzeugung synchron auf dem aktuellen Thread erfolgte, kehrt dieser Aufruf sofort zurück. |
Beispiel 7.11. Asynchrones Erzeugen eines FontManager
s
Ähnlich zum vorhergehenden Beispiel wird hier ebenfalls ein FontManager
erzeugt. Inhalt und ID sind identisch. Die Erzeugung erfolgt jedoch asynchron im
Hintergrund.
String id = "any unique identifier";
FontManagerFuture future = FontManagers.startBuilding(id)
.asynchronously()
.withSystemFonts()
.finish();
…
FontManager fontManager = future.get();
|
Während der FontManager im Hintergrund erzeugt wird, kann auf dem aktuellen Thread weiterer Code ausgeführt werden. |
|
Der Aufruf kehrt erst zurück, wenn die Erzeugung im Hintergrund vollständig abgeschlossen ist. |
Beispiel 7.12. Erzeugen eines FontManager
s mit den Standard-14 Fonts
Das folgende Beispiel zeigt die Erzeugung eines FontManager
s, der Open
Source Alternativen für die allgemein gebräuchlichen Standard-14 Fonts enthält.
Die jar
-Datei welche die Font-Dateien
beinhaltet, muss dabei auf dem Klassenpfad liegen.
FontManagers.startBuilding("Standard-14 Font Manager") .asynchronously() .withStandard14() .finish();
Beispiel 7.13. Erzeugen eines FontManager
s aus verschiedenen Quellen
Der folgende Quelltext zeigt einige Möglichkeiten zur Definition der Inhalte
eines FontManager
s. Die zu verwendeten Font-Dateien können auf verschiedene
Wege bekannt gemacht werden.
FontManagers.startBuilding("big Font Manager") .asynchronously() .withStandard14() .withSystemFonts() .with(Paths.get("path/to/local/fonts")) .with(new File("additional/local/fonts")) .with(new FileInputStream("some/folder/arial.ttf")) .finish();
Beispiel 7.14. Typisches Pattern zur Implementation einer FontFactory
Das Beispiel zeigt das Grundgerüst einer typischen FontFactory
Realisierung. Die asynchrone Erzeugung des FontManager
s sollte generell
möglichst frühzeitig im Programmablauf angestoßen werden. Der Einfachheit halber
wird sie hier im Konstruktor dargestellt. Alternativ könnte das
FontManagerFuture
dem Konstruktor von außen übergeben werden. Da der Aufbau
des FontManager
s im Hintergrund läuft, kann parallel dazu die weitere
Initialisierung der Anwendung durchgeführt werden.
Der Abruf des FontManager
s vom FontManagerFuture
erfolgt zum
spätestmöglichen Zeitpunkt – also dann, wenn ein Font benötigt wird damit ein
Dokument geladen werden kann. Falls die asynchrone Erzeugung des FontManager
s
zu diesem Zeitpunkt bereits abgeschlossen ist, entsteht keine zusätzliche
Wartezeit. Andernfalls pausiert der Dokument-Ladevorgang bis der FontManager
zur Verfügung steht. In diesem Fall kann es zu einmaligen, initialen
Verzögerungen in der Dokumentanzeige kommen.
public class ExampleFontFactory implements FontFactory { final FontManagerFuture fontManagerFuture; public ExampleFontFactory() { fontManagerFuture = FontManagers.startBuilding("example font manager") .asynchronously() .withStandard14() .finish(); } @Override public Font create(FontAttributeSet fontAttributeSet, Map<String, Object> scope) { Font resultingFont; try { final FontManager fontManager = fontManagerFuture.get(); resultingFont = findTheBestFont(fontManager, fontAttributeSet, scope); } catch (InterruptedException | ExecutionException e) { resultingFont = new BasicFontFactory().create(fontAttributeSet, scope); } return resultingFont; } private Font findTheBestFont(FontManager fontManager, FontAttributeSet fontAttributeSet, Map<String, Object> scope) { … } }
|
Implementations-spezifische Logik um einen geeigneten Font zu ermitteln. |
Beispiel 7.15. Verwendung der ChainedFontFactory
Die ChainedFontFactory
ist ein Hilfsmittel zur Trennung von Zuständigkeiten
in FontFactory
-Implementationen. Der folgende Quelltext zeigt die Erstellung
einer ChainedFontFactory
, die im Nachgang für Dokument-Ladevorgänge nutzbar
ist.
Die ChainedFontFactory
nimmt in ihrem Konstruktor weitere
FontFactory
-Instanzen entgegen. Sie reagiert auf Font-Anfragen, indem sie
diese nacheinander an die übergebenen Instanzen delegiert. Sobald ein Wert
ungleich null
vorliegt, wird dieser als Ergebnis der
ChainedFontFactory
zurückgegeben. Die verbleibenden Instanzen werden nicht
mehr befragt.
Da die FontFactory
, die für einen Ladevorgang registriert ist, zwingend
einen validen Font zurückgeben muss, sollte dies durch die übergebenen Instanzen
sichergestellt werden. Die ChainedFontFactory
selbst nimmt keine Validierung
vor. Dies bedeutet, dass die angefragten Instanzen null
zurückgeben dürfen um damit zu signalisieren, dass weitere Instanzen befragt
werden sollen. Die letzte FontFactory
muss jedoch zwingend einen Font
zurückgeben damit das Gesamtergebnis nicht null
wird. Im
Beispiel kommt die BasicFontFactory
zum Einsatz, die unabhängig von der
Anfrage stets einen Fallback-Font zurückliefert.
FontManagerFuture fontManagerFuture = …; FontFactory fontFactory = new ChainedFontFactory( new FuzzyStandard14FontFactory(fontManagerFuture), new FuzzyStyleFontFactory(fontManagerFuture), new BasicFontFactory() );
|
Die BasicFontFactory gibt immer einen Font zurück. Da die FontFactory, die für den Lesevorgang registriert ist, immer einen Font zurückgeben muss, empfiehlt es sich eine Chain zu verwenden, in der die BasicFontFactory als letzte Instanz registriert ist. |
Beispiel 7.16. Generieren von Log-Meldungen via Logger
Zur Entwicklungszeit und im Fehlerfall ist es hilfreich, Informationen darüber
zu erhalten, welche Entscheidungen im Font-Auswahlprozess getroffen werden.
Insbesondere wenn eine ChainedFontFactory
zum Einsatz kommt, steigt die
Gesamtkomplexität durch das Zusammenspiel der verschiedenen Klassen. Das
Beispiel zeigt, wie Log-Ausgaben bei Bedarf angefordert werden können. Falls
kein Logging aktiviert ist, wird der Logging-Code nie durchlaufen und es
entsteht somit kein unnötiger Mehraufwand.
FontFactory fontFactory = …; Logger logger = LoggerFactory.getLogger(getClass()); if (logger.isDebugEnabled()) { fontFactory = new ActivateLoggingFontFactory(fontFactory, logger); }
|
Falls der verwendete Logger mindestens auf Level DEBUG steht, wird das Logging von Anfragen an die FontFactory aktiviert. Die ActivateLoggingFontFactory umhüllt eine andere FontFactory und initialisiert das Generieren von Log-Meldungen. |
Beispiel 7.17. Generieren von Log-Meldungen mit frei definierbarem Ziel
Ähnlich wie im vorhergehenden Beispiel werden auch hier Log-Meldungen
generiert. Zusätzlich wird die Möglichkeit genutzt, das Ziel der Log-Strings
selbst zu bestimmen. Es muss nicht zwingend ein Logger
verwendet werden.
Stattdessen kommt hier der Standard Error Stream zum Einsatz. Auch Logging in
eine Datenbank oder ein anderes entferntes System wäre möglich. Da der Code
während des Ladevorgangs ausgeführt wird, muss dafür gesorgt werden, dass
möglichst wenig Zeit investiert wird. Langlaufende Operationen, wie
beispielsweise eine Netzwerk-Kommunikation, müssen auf einen anderen Thread
ausgelagert werden.
FontFactory fontFactory = …; LogSink systemErrLogSink = new LogSink() { @Override public void log(String logMessage) { System.err.println(logMessage); } }; String loggingFontFactoryID = "My Logging Font Factory"; final ActivateLoggingFontFactory activateLoggingFontFactory = new ActivateLoggingFontFactory(fontFactory, systemErrLogSink, loggingFontFactoryID);
|
Ziel der Log-Ausgaben. Die anonyme Klasse leitet alle Ausgaben nach System.err weiter, könnte aber auch andere Ziele ansprechen. |
|
Optionaler, frei wählbarer Identifikations-String. Insbesondere falls mehrere FontFactory Instanzen existieren, können sie durch diese ID in den Log-Ausgaben identifiziert werden. |
|
Umhüllen der eigentlichen FontFactory um Log-Ausgaben zu aktivieren. Sämtliche Aufrufe, die über die ActivateLoggingFontFactory laufen, generieren automatisch Log-Meldungen. |
Beispiel 7.18. Setzen der FontFactory
für einen Lesevorgang
Das Beispiel zeigt, wie eine FontFactory
für alle folgenden Lesevorgänge
einer Reader
-Instanz gesetzt werden kann. Für andere Reader
-Instanzen muss
die unabhängig davon erneut erfolgen.
FontFactory fontFactory = …; Reader reader = …; FontFactoryReaderSettings settings = reader.getSettings(FontFactoryReaderSettings.class); settings.setFontFactory(fontFactory); reader.read(…);
|
Die veränderten Settings sind für alle weiteren read(…)-Aufrufe gültig. |
Beispiel 7.19. Setzen der FontFactory
für alle Lesevorgänge mittels
ComponentConfigurer
Um nicht jede Reader
-Instanz einzeln konfigurieren zu müssen, existiert ein
allgemeingültiger Konfigurationsweg. Dazu wird ein ComponentConfigurer
definiert, der vor der ersten Erzeugung einer Reader
-Instanz registriert sein
muss. Die Registrierung erfolgt global über die Methoden der Klasse Jadice
.
Als JUnit Test formuliert zeigt das Beispiel, dass der ComponentConfigurer
für jede Instanz von FontFactoryReaderSettings
, die über die Methoden der
Klasse Jadice
erzeugt wird, ausgeführt wird. Da der Reader
seine
Settings-Objekte auf diesem Weg erzeugt, ist die FontFactory
-Instanz für
jeden Lesevorgang gesetzt.
@Test public void testThat_configurer_configuresFontFactoryReaderSettings() { final FontFactory fontFactory = new BasicFontFactory(); ComponentConfigurer<FontFactoryReaderSettings> configurer = new ComponentConfigurer<FontFactoryReaderSettings>() { @Override public void configure(FontFactoryReaderSettings settings) { settings.setFontFactory(fontFactory); } }; Jadice.registerComponentConfigurer(configurer, FontFactoryReaderSettings.class); FontFactoryReaderSettings settings1 = Jadice.create(FontFactoryReaderSettings.class); assertThat(settings1.getFontFactory(), is(sameInstance(fontFactory))); Reader reader = new Reader(); FontFactoryReaderSettings settings2 = reader.getSettings(FontFactoryReaderSettings.class); assertThat(settings2.getFontFactory(), is(sameInstance(fontFactory))); }
|
Configurer als anonyme Klasse, die auf jeder Instanz von FontFactoryReaderSettings dieselbe FontFactory setzt. |
|
Globales Registrieren des Configurers. Er wird nun für jede Instanz aufgerufen, die über den Jadice.create(…) Mechanismus erzeugt wird. Dies betrifft im Besonderen die FontFactoryReaderSettings, die von Reader-Instanzen automatisch erzeugt werden. |
Beispiel 7.20. Beispielkonfiguration für Fonts
Eine beispielhafte Komplett-Konfiguration für das Laden von Fonts wird in den
Klassen FontEnvironments
SwingFontEnvironments
bereitgestellt.
Für die Integration des Konfigurationsbeispiels genügt ein Aufruf der Konfiguration zu Beginn der Anwendung:
FontEnvironments.configureFontEnvironment();
Sollten zudem noch GUI-Komponenten verwendet werden kann man die Beispielkonfiguration wie folgt übernehmen:
SwingFontEnvironments.configureFontEnvironment();