Graal(VM) – Versuch eines Überblicks

 Java  Kommentare deaktiviert für Graal(VM) – Versuch eines Überblicks
Jul 142019
 

Die Themen Graal bzw. GraalVM beschäftigen mich schon eine ganze Weile, und wie ich erneut auf dem Java Forum Stuttgart 2019 bemerkt habe, hat das Graal-Universum derart viele Facetten, die sich zudem alle in einem unterschiedlichen Stand der Nützlichkeit und Nutzbarkeit befinden, so dass ich hier mal versuchen werde, einen Überblick zu geben.

Das Graal-Universum (auch gerne “GraalVM ecosystem” genannt) ist ein Oracle-Projekt, hervorgegangen aus zahlreichen Forschungsprojekten der Oracle Labs in Kooperation mit der Uni Linz – früher hieß das Projekt “Metropolis” (und noch früher Maxine VM) und hatte ursprünglich neben Forschung angeblich nur das Ziel, einen JIT-Compiler für die Hotspot-JVM in Java neu zu schreiben, weil vor allem der in C++ geschriebene “C2” (auch als “HotSpot-Compiler der Server-JVM” bekannt) das Ende seiner Erweiterbarkeit und Wartbarkeit erreicht hatte. Inzwischen ist sehr viel mehr daraus geworden.

Übrigens steht “Graal” für “General Recursive Applicative and Algorithmic Language”. Was ein Glück, dass niemand auf die daraus eigentlich resultierende Schreibweise “GRAAL” besteht. Ein weiterer wichtiger Hinweis: das Wording wurde über die Zeit verändert, bitte nicht verwirren lassen von Graal vs. GraalVM vs. Graal-Compiler vs. Graal-JIT. Derzeit scheint man sich auf “GraalVM” für das Gesamtpaket entschieden zu haben, mit “Graal” als Kurzform, wenn man drüber spricht.

Die (JVM-)Welt vor GraalVM

Viel wurde geschrieben zur JVM, zu Bytecode, zu Interpretern, zu JIT-Compilern und der dynamischen Recompilierung à la HotSpot. Vor GraalVM war die HotSpot-JVM von Sun/Oracle die übliche Laufzeitumgebung für Bytecode aller Art (Dinge wie Excelsior JET, IBM/Eclipse J9, Azul Zing oder sogar gcj aus früheren Zeiten waren und sind ja eher Exoten). Die HotSpot-JVM hat im Prinzip drei Möglichkeiten, um Bytecode zu behandeln: entweder er wird interpretiert, oder er wird mit dem C1-Compiler (auch “client compiler” genannt) in Maschinencode übersetzt (das geht schnell, ist aber wenig optimierter Code), oder er wird mit dem C2-Compiler (auch “server compiler” genannt) in Maschinencode übersetzt (das dauert etwas länger, ist aber sehr gut optimiert, und kann zur Laufzeit, wenn eine Methode beispielsweise sehr häufig aufgerufen wird, weiter optimiert werden – das ist der “HotSpot”-Aspekt der JVM). Das Zusammenspiel aus C1-C2 wird oft auch “tiered compilation” genannt.

Nun ist das C++-Duo C1-C2 schon gut 20 Jahre alt und nach verschiedenen Aussagen diverser Beteiligter am Ende von Weiterentwicklung und Wartbarkeit angekommen. C2 wurde in den letzten Jahren nur noch mit spitzen Fingern angefasst, es wurden keinerlei Verbesserungen an der Architektur oder der prinzipiellen Optimierungsmöglichkeiten vorgenommen, nur Bugfixing und diverse Optimierungen im Bereich Intrinsics (also das Erkennen spezifischer Codepatterns und Verwendung von optimiertem Maschinencode um diese direkt zu ersetzen), was aber als Backend-Optimierung für jede unterstützte Prozessorarchitektur separat gemacht werden musste (oder der Einfachheit halber gleich unterblieb).

Man könnte argumentieren, dass das (also Stabilität und wenige Änderungen) auch gut so ist: durch die Ausführung als native code in der JVM besteht schließlich immer die Gefahr, bei einem unerwarteten Fehler die komplette JVM mit einem Coredump nach einem Segmentation Fault herunterzureißen. Und schließlich muss man ja zugeben, dass der C2 insbesondere Java-Bytecode in wirklich effizienten Maschinencode überführt, der für viele Anwendungen den Performancevergleich mit statisch compilierten Sprachen wie C oder C++ nicht zu scheuen braucht, und teilweise aufgrund seiner dynamischen Natur auf Basis von Informationen, die erst zur Laufzeit zur Verfügung stehen, diese sogar performancetechnisch übertreffen kann.

Diese uns allen bekannte JVM-Welt hat aber natürlich auch bekannte Schwächen, so dass das Festhalten am Status Quo kaum als eine valide Vorgehensweise für die Zukunft erscheint. Da ist zum einen der nicht zu vernachlässigende Performancenachteil beim Startup einer Anwendung – es dauert seine Zeit, bis die entscheidenden “heißen” Codepfade durch C2 entsprechend optimiert wurden, ein Prozess, der oft als “heating up the JVM” oder ähnlich bezeichnet wird. Und während dieser Zeit genehmigt sich die JVM ja durchaus auch erkleckliche Mengen an Speicher, und der Garbage Collector darf regelmäßig aufräumen. So schön das Prinzip einer virtuellen Maschine auch sein mag, in gewissen Anwendungsszenarien wie z.B. Desktop-Anwendungen oder Cloud-Services sind die Nachteile unübersehbar.

Dazu kommt, dass sich mit der Zeit herausgestellt hat, dass die Techniken des C2 zu nah an Java und der Art und Weise, wie javac Bytecode erzeugt, gebaut sind. Das führt zu suboptimalen Ergebnissen bei der Ausführung von Bytecode aus anderen Quellen, sei es Scala, JRuby, Jython oder Clojure.

Und dann ist da natürlich das Problem des “Systembruchs” bei der Weiterentwicklung des JITs – man muss immer zwei Welten verstehen, entwickeln, analysieren und debuggen – die native C++-Welt und die Java-Welt. Qualifiziertes Personal, das in beiden Welten zuhause ist, ist naturgemäß schwer zu finden.

Jedenfalls kann man nicht wegdiskutieren, dass das Großthema Performance in den Teilbereichen “startup” und “footprint” mit der bisherigen Strategie kaum ausreichend gelöst werden kann, vor allem wenn im Serverbereich nicht mehr länger große, langlaufende Prozesse das übliche Architekturmuster sind, sondern stattdessen kurzlaufende, containerisierte, über ihre schiere Anzahl skalierende Microservices. Bisher wurden diese Themen nur im Rahmen des gewohnten Umfelds angegangen – Class Sharing, Modularisierung des JDK, AOT seit Java 9. Mit begrenztem Erfolg.

Graal der JIT-Compiler aka “GraalVM compiler”

Angeblich beginnt die Geschichte von Graal(VM) etwa 2009 mit einer Portierung von C1/C2 nach Java durch Thomas Würthinger. Heute kann man den Graal-JIT-Compiler recht einfach in JDK 8 oder JDK 11 integrieren, kann also von jedem Java-Entwickler sehr einfach ausprobiert werden. So kann man in Ruhe für seinen spezifischen Workload herausfinden, ob die fortgeschrittenen Optimierungstechniken tatsächlich einen Laufzeitvorteil bedeuten, und wie groß dieser gegebenenfalls ausfällt.

Vor geraumer Zeit (Java 8, zunächst aber nicht öffentlich dokumentiert, offiziell dabei seit Java 11) wurde das JVMCI (JVM Compiler Interface, JEP 243) eingeführt, eine (hoffentlich langzeit-)stabile Schnittstelle der JVM zum Andocken alternativer JIT-Compiler. Graal kann genau diese Schnittstelle nutzen.

Wo bietet Graal nun Vorteile gegenüber dem bisherigen C2? Insbesondere die sogenannte “Escape Analysis” scheint sehr viel leistungsfähiger zu sein. D.h. der Compiler ist viel besser beim Erkennen von Objekten, deren Gültigkeitszeit z.B. auf Methodenebene beschränkt ist und deshalb problemlos auf dem Stack statt auf dem Heap allokiert werden kann. Das hat doppelten Nutzen, denn die Stack-Allokation ist schneller und der GC muss hinterher nicht aufräumen. Besonders bei Scala-Code scheint diese Verbesserung für erhebliche Performanceverbesserungen zu sorgen. Aber auch das Inlining von Code soll mit Graal besser funktionieren.

Die Tatsache, dass Graal in Java geschrieben ist, hat interessante Konsequenzen. Zum einen wird nun der JIT-Compiler selbst in der JVM ausgeführt und optimiert sich so mit der Zeit selbst. Speicherallokation für den Compiler findet auf dem JVM-Heap statt anstelle aus dem Native-Pool zu kommen. Da nun auch die Performance des Compilers einer Warm-Up-Zeit unterliegt, verändert sich auch das Performance-Profil zur Ausführungszeit. Und der GC ist natürlich potenziell stärker beschäftigt, weil er sich nun sowohl mit der eigentlichen Anwendung als auch mit dem Compiler beschäftigen muss.

Um dieses Szenario zu nutzen, kann man einfach die GraalVM oder einen der OpenJDK-Builds vom GraalVM-Team nutzen und entsprechend konfigurieren. Im Prinzip ein “drop-in replacement” für das gewohnte JDK.

Und was ist libgraal?

libgraal ist der Versuch, den Graal-JIT-Compiler mittels GraalVM-native-image (dazu später mehr) in eine Bibliothek zu überführen, die das Performance-Problem des Compile-Vorgangs des Graal-JIT-Compilers in der JVM lösen soll. Da schon alles in nativem Code vorliegt, muss sich der JIT-Compiler nur noch um den Anwendungscode kümmern und nicht mehr um sich selbst. Im Prinzip wird dadurch Graal zu einem – was das Laufzeitverhalten angeht – sehr ähnlichen JIT-Compiler wie der klassische C1 und C2 in der HotSpot-JVM. Langsame JIT-Compilierung während der JVM-Warm-Up-Phase wird dadurch verhindert.

Die aktuellen Versionen von GraalVM sowie die GraalVM-OpenJDK-Builds enthalten allesamt libgraal out-of-the-box.

GraalVM und die AOT-Compilierung

Seit Java 9 gibt es im JDK ein als experimentell markiertes Tool namens jaotc, das (eine recht frühe Version von) Graal als Backend verwendet. Die Implementierung erfolgte im Rahmen von JEP 295, dort kann man viele der Einschränkungen nachlesen. Im Prinzip gibt man jaotc vor, welcher Code vorcompiliert werden soll, und es wird ein Binär-Blob erzeugt, der dann den sonst durch den JIT-Compiler erzeugten Maschinencode enthält. Man spart also die Zeit, die der JIT-Compiler sonst zur Laufzeit braucht, um den Code zu compilieren.

Es gibt bei jaotc jede Menge Einschränkungen, die den Einsatz in der Praxis wirkungsvoll verhindert haben – zumindest in der Breite. Immerhin gab es bei Java 10 einige Bemühungen, um diverse gravierende Einschränkungen wie “funktioniert nur unter Linux-x64” aufzuheben. GraalVM geht heute aber noch einen großen Schritt weiter mit “native-image”.

GraalVM und das “Native Image”-Feature

Um Memory-Footprint und Startup-Zeit zu minimieren, unterstützt GraalVM die Möglichkeit, ein “Native Image” zu erzeugen. Simpel gesagt: ein standalone ausführbares Programm, das keine separate JVM benötigt. Die bisherigen Releaseversionen von GraalVM inklusive der derzeit aktuellen 19.1.0 erlauben die Erstellung eines solchen “Native Images” für Linux und macOS. Dazu wird neben der GraalVM-Distribution auch noch eine native Toolchain für das Betriebssystem benötigt, für Linux beispielsweise gcc nebst Bibliotheken wie glibc und zlib. Für die noch sehr experimentelle Windows-Unterstützung wird eine Installation von “Windows SDK for Windows 7” benötigt.

Es gibt ein paar Dinge neu zu durchdenken, wenn man ein “Native Image” erzeugt. Beispielsweise die Frage, was eigentlich schon zur Compilezeit aufgelöst wird und was erst zur Laufzeit. Stichwort “Initialisierung statischer Member”. Man überlege sich, inwieweit sich die Semantik des Programms ändert, wenn in einem static initializer eine Variable mit “new Date()” belegt wird und dies einmal zur Compilezeit passiert. Für solche Fälle werden Compile-Time-Switches angeboten.

Prinzipbedingt gibt es einige Limitierungen beim native-image-Feature. Wichtig ist die “closed-world assumption”. GraalVM muss bei der statischen Compilierung schließlich entscheiden können, welche Klassen für das Image eingebunden werden müssen. Das schließt übliche Java-Mittel wie dynamisches Nachladen von Klassen zur Laufzeit aus, deren Notwendigkeit auch erst zur Laufzeit auftaucht – typische Service-Lookup-Konstruktionen, beispielsweise im OSGi-Umfeld, gehören zu diesem Problemkreis. Ein Feature, um über einen Tracing Agent auch zur Laufzeit dynamisch nachgeladene Klassen sicher zu identifizieren, befindet sich derzeit in der Entwicklung. Aber auch das würde natürlich vollständig dynamisches Nachladen nur dann erfassen, wenn alle entscheidenden Codepfade auch tatsächlich durchlaufen werden, während der Tracing Agent aktiv ist.

Erstaunlicherweise ist trotzdem partieller Support für Reflection verfügbar, was uns zum nächsten Teilthema führt…

GraalVM und die Substrate VM

Auch nativer Code braucht natürlich, wenn die Semantik eines Java-Programms möglichst unangetastet bleiben soll, eine gewisse Laufzeitumgebung. Die wird im Falle eines GraalVM-erzeugten “Native Image” durch die eingebettete Substrate VM zur Verfügung gestellt. Dazu gehören Runtime-Routinen und der Garbage Collector. Die Komponenten der Substrate VM sind in Java geschrieben.

GraalVM und Truffle

Bisher waren die Punkte sehr Java-spezifisch, oder genauer gesagt Bytecode-spezifisch – alles problemlos nutzbar für das JVM-Bytecode-Ökosystem, also neben Java auch Kotlin, Scala, Groovy, Clojure und Konsorten. So weit, so unspektakulär. Andere Programmiersprachen haben häufig Bytecode-spezifische Forks als Implementierung wie Jython oder JRuby, und das JDK hatte bekanntlich seinen eigenen JavaScript-Interpreter intus namens Rhino (ab Java 6) und Nashorn (ab Java 8).

Aber GraalVM bietet mehr. Das Truffle-Framework erlaubt die einfache Implementierung von Interpretern, die dann vollautomatisch zu einem Bestandteil der GraalVM werden können – quasi eine automatische Generierung eines Compilers aus einem Interpreter.

Interpreter für Sprachen wie JavaScript, Ruby, R und Python sind bereits heute in verschiedenen Stadien der Fertigstellung bereit zu Experimenten. Die JavaScript-Implementierung scheint am weitesten fortgeschritten und umfasst bereits den Sprachumfang von ECMAScript 2019. Das war übrigens das Hauptproblem mit den Java-internen JavaScript-Lösungen – sie konnten mit der schnellen Weiterentwicklung von ECMAScript nicht mithalten und waren schnell veraltet und schwer wartbar.

Eine gute Zusammenfassung in wenigen Worten: “Truffle makes it easy, Graal makes it fast.” Wer schon mal versucht hat, für eine einfache Sprache einen Interpreter zu entwickeln, weiß, dass das durchaus machbar ist. Ein optimierter Interpreter ist schon schwieriger. Aber einen gescheit optimierenden Compiler dafür zu schreibe, ist sauschwer. Soll der auch noch mit in anderen Sprachen geschriebenem Code kommunizieren, wird es langsam wirklich komplex. Das GraalVM-Ökosystem verspricht hier eine einfach handhabbare und trotzdem sehr leistungsfähige und performante Lösung.

GraalVM als polyglotte VM

Nun wäre es ja schon ein echter Fortschritt, die GraalVM als universelle Laufzeitumgebung für allerlei Sprachen zur Verfügung zu haben. Beispielsweise könnte man sich für die gängigen Interpreter-Sprachen wie eben Ruby oder Python oder auch Lua die zeitaufwändige Entwicklung einer effizienten Laufzeitumgebung schlicht sparen – warum immer wieder neu einen JIT-Compiler implementieren, wenn es in GraalVM schon einen gibt? Aber wenn das nun schon möglich ist, wie wäre es denn dann, wenn man die Sprachen beliebig mischen könnte?

Das wäre natürlich phantastisch, und GraalVM bietet genau das. Es existieren Schnittstellen, um die Sprachen kontrolliert miteinander verbinden zu können. Und zwar, weil alle in demselben VM-Kontext laufen, quasi ohne zusätzlichen Overhead. So werden Java-Objekte für JavaScript-Skripte zugreifbar, Methoden aufrufbar, das volle Programm. Die technische Magie dazu steckt im “polyglot interoperability protocol” des Truffle-Frameworks, das genau diese Dinge standardisiert und damit diese Interoperabilität automatisch sicherstellt.

Der alte Traum von pick-and-mix bezüglich unterschiedlicher Programmiersprachen mit ihren spezifischen Stärken und leistungsfähigen Bibliotheken, hier wird er Wirklichkeit.

Wirklich cool ist beispielsweise eine für GraalVM erweiterte Variante von VisualVM. Man kann damit den laufenden Code in Ruby, R oder JavaScript genau untersuchen hinsichtlich der Threads und der Objekte auf dem Heap.

Und was ist Sulong?

Sulong ist eine weitere Schicht der GraalVM-Welt – ein High-Performance-Interpreter für LLVM Bitcode. Das erlaubt es der GraalVM, Programmcode, der in diversen Sprachen auf Basis der LLVM-Compiler-Infrastruktur erzeugt wird (besonders prominent: C, C++, Objective-C, Swift und Rust), ebenfalls auszuführen. Mit voller Interoperabilität mit allen anderen unterstützten Sprachen des GraalVM-Universums.

Ich erkenne hier eine leichte Ironie, schließlich hat Azul Systems für ihre Zing JVM einen neuen JIT-Compiler namens “Falcon” entwickelt, der auf der LLVM-Compiler-Infrastruktur basiert. Quasi genau andersrum.

Community Edition vs. Enterprise Edition

Oracle ist nicht gerade bekannt dafür, Projekte aus reiner Menschenfreundlichkeit kostenlos unter die Leute zu bringen. Demzufolge strebt man auch im Graal-Universum einen RoI an, und der heißt “GraalVM Enterprise Edition”. Es gibt einige Optimierungen, die nur in der Enterprise-Variante zur Verfügung stehen, Stand heute scheint das vor allem die Autovektorisierung zu sein. Man kann die Enterprise-Variante jederzeit zu Evaluierungszwecken herunterladen und so prüfen, ob das beim eigenen Workload etwas bringt – die Evaluierungsversion darf jedoch nicht in Produktion verwendet werden.

Ob und wann Optimierungen von der Enterprise-Version in die Community-Version wandern, muss abgewartet werden. Oracle wird sicherlich anstreben, immer einen Respektabstand zwischen den beiden Versionen zu wahren und vor allem Optimierungen für die Großen der Branche möglicherweise exklusiv für die Enterprise Edition reservieren. Letztlich muss der Performance-Gewinn durch die Enterprise-Edition ja im realen Leben gegen eingesparte Kosten z.B. im Cloud-Umfeld gerechnet werden – der Business-Case muss möglichst leicht nachvollziehbar sein, wenn man Lizenzen verkaufen will.

Bezüglich der Lizenzsituation bleibt festzuhalten, dass praktisch alle GraalVM-Komponenten unter GPL2 mit Classpath-Exception stehen.

Fazit, Status Quo, Ausblick, Glaskugel

Das Potenzial ist gewaltig – viele Schwächen des Java-Ökosystems können mit GraalVM gelöst oder zumindest gemildert werden. Dazu kommt mit der Idee der polyglotten VM der alte Traum der völligen Austauschbarkeit von Programmiersprachen und Bibliotheken in greifbare Nähe. Mit der “Native Image”-Möglichkeit wird Java auch wieder (oder zum ersten Mal?) eine sehr gute Lösung für klassische Apps und Anwendungen neben den unbestreitbaren Vorteilen für servserseitig kurzlaufende Services in einem containerisierten Cloudumfeld.

Was hält uns davon ab, genau jetzt vollständig in die GraalVM-Welt einzutauchen? Aus meiner Sicht ist das GraalVM-Ökosystem im Moment in einem uneinheitlichen Zustand. Während das Szenario “GraalVM als JIT-Plugin für die HotSpot-JVM” stabil ist, ist der Rest eher von wechselhafter Stabilität. Vor allem das Feature “native image” scheint im Moment eher limitiert und zurecht als “Early Adopter technology” gekennzeichnet, nicht zuletzt durch die Beschränkung auf Linux und macOS – Windows ist für diesen Anwendungsfall als “experimental” markiert. Wenn man sich die GitHub-Issues zu diesem Thema durchliest, wird klar, dass da noch eine Menge Feinarbeit zu leisten ist. AOT hingegen scheint weitgehend stabil zu sein, ist es doch seit Java 9 Teil des JDK.

Was die Polyglot-Fähigkeiten angeht, scheint die JavaScript-Implementierung recht stabil und zudem vollständig zu sein – Node.js ist ein sehr wichtiges Zielszenario, und man braucht durch die Abkündigung der Nashorn-Engine in Java 11 auch beizeiten einen Ersatz dafür. Bei Ruby, R, Python und LLVM-Bitcode steht eher noch ein Fragezeichen bezüglich Vollständigkeit, Performance und Stabilität. Vom technischen Standpunkt aus ist eine Out-of-the-box-Lauffähigkeit von realen Python-Programmen natürlich auch deutlich schwerer zu bewerkstelligen als bei JavaScript, weil das Python-Ökosystem bei vielgenutzten Bibliotheken voller native code zwecks besserer Performance steckt. NumPy und SciPy seien hier als Beispiele genannt. Bei R ist es ähnlich, wo die performance-relevanten Berechnungen oft in nativem Fortran-basierten Code passiert.

Angesichts der kürzlich erfolgten Abkündigung von Nashorn, der JavaScript-Engine im JDK, dürfte eine stabile und performante JavaScript-Fähigkeit ganz oben auf der Prioritätenliste zu sein. Außerdem scheint GraalVM als Ausführungsplattform für das Node.js-Universum auch attraktiv zu sein, vor allem natürlich wegen der einfachen und performanten Interoperabilität zwischen Java und JavaScript.

Die Native-Image-Fähigkeit ist wie gemacht für Microservices, und so gibt es bereits drei seriöse Frameworks, die GraalVM zu diesem Zweck nutzen: Quarkus, Micronaut und Helidon. Die Benchmarks bezüglich Memory-Footprint und Startup-Zeiten sehen schon beeindruckend aus.

Sehr interessant ist die Möglichkeit, die Native-Image-Fähigkeit für Plattformen zu verwenden, wo keine virtuelle Maschine zulässig ist – OK, “Plattformen” ist hier meines Wissens zu Unrecht die Mehrzahl, denn ich kenne nur iOS, das unter dieser willkürlichen Apple-Entscheidung zu leiden hat. Hier ein Artikel, der die Möglichkeiten beleuchtet. Und zurecht anmerkt, dass es schon viele vergebliche Versuche gab, iOS für das Java-Ökosystem einfach zugänglich zu machen.

Nicht zuletzt wäre die Native-Image-Fähigkeit für die wenigen verbliebenen Java-Entwickler, die Desktop-Anwendungen schreiben, eine schöne Sache. Eine einfache exe hat bei der Distribution von Software einfach Vorteile gegenüber dem Bundling einer kompletten Java-Runtime-Umgebung, möglicherweise nebst Einsatz von Obfuscator und exe-Wrapper.

Ein weiterer hier nicht weiter beleuchteter Anwendungszweck ist das Embedden von GraalVM-Technologie in andere Plattformen. Es werden die Oracle-Datenbank und MySQL derzeit genannt, mit diesem Anwendungsfall habe ich mich nicht näher beschäftigt.

Die Entwicklung schreitet im Moment zügig voran – wer am Ball bleiben will, sollte regelmäßig die Release Notes für neue Versionen durchlesen, man bekommt dann ein ganz gutes Gefühl für die Reife von manchem Szenario.

In der optimal vergraalten Welt der Zukunft wäre es ein Leichtes, mit dem Truffle-Framework seine persönliche Lieblingssprache in verhältnismäßig kurzer Zeit aus dem Boden zu stampfen und sofort eine komplette Toolchain inklusive hochoptimierender VM und nativen Code erzeugendem Compiler zur Verfügung zu haben. Inklusive voller Interoperabilität mit den Ökosystemen aller relevanten Programmiersprachen von Java über C++ bis Python. Die Grundbausteine dafür sind vorhanden und durchaus schon von hoher Qualität, aber es fehlt noch reichlich Feinschliff. Ich fürchte aber, dass die Lösung des Gesamtproblems der 80-20-Regel folgt. Wenn nicht gar 90-10.

Aber es wäre in einem ausgereiften Endzustand schon nahe am heiligen Gra(a)l. Ich hoffe, man verzettelt sich nicht in der Vielfalt der Möglichkeiten. Jedenfalls wäre Manpower in der GraalVM-Ecke m.E. deutlich besser aufgehoben als bei mancher heißdiskutierter Spracherweiterung seit Java 9. Ich sage mal “Text Blocks”. “Type Inference”. Private Methoden in Interfaces. Oder JDK-Erweiterungen wie jshell oder Epsilon GC, deren überragende Nützlichkeit sich auf den ersten und auch zweiten Blick nicht direkt erschließt.

Andere Quellen

Erster Anlaufpunkt ist natürlich die GraalVM-Homepage. Sehr empfehlenswert sind YouTube-Videos von Thomas Würthinger und Christian Thalinger (als früherer Entwickler des C2 natürlich spezialisiert auf den GraalVM-JIT aka GraalVM compiler, der früher verwirrenderweise Graal JIT oder Graal compiler hieß).

Wer ganz tief einsteigen will – die Sourcen werden auf GitHub gehostet.

Anlass für diesen Blog-Eintrag war ein Übersichtsartikel auf Heise Developer, der es doch etwas an Tiefe vermissen lies und durch Vermischung der unterschiedlichen Aspekte beim Uneingeweihten möglicherweise für Verwirrung sorgt. Ich überlasse es dem geneigten Leser zu entscheiden, ob mein Artikel eventuell noch wirrer und verwirrender ist.

Java Forum Stuttgart 2019

 Java  Kommentare deaktiviert für Java Forum Stuttgart 2019
Jul 062019
 

Über meine Gründe, das Java Forum Stuttgart zu besuchen, habe ich bereits 2017 was geschrieben, und daran hat sich nichts geändert. 2018 habe ich aufgrund wenig vielversprechender Themenauswahl mal sausen lassen, also war es 2019 mal wieder an der Zeit.

Die Rahmenbedingungen waren dieselben: Liederhalle, top organisiert, Verpflegung OK (auch wenn ich wohl nie verstehen werde, warum jemand Coke Light anbietet statt Coke Zero, aber über Geschmack kann man nicht streiten – vielleicht war es ja wenigstens billiger), abwechslungsreiches Vortragsprogramm. Und ein Code of Conduct scheint heutzutage wohl unvermeidlich, natürlich in gendergerechter Sprache anstatt in richtigem Deutsch. Man könnte den ganzen Sermon inhaltlich auch schneller auf den Punkt bringen: “Es gelten die Regeln der westlichen Zivilisation und des allgemeinen Anstands”. Oder “Verhalte Dich stets so, dass Deine Mutter auf Dich stolz wäre.”

Zum eigentlichen Thema. Gab es diesmal einen der typischen Themenschwerpunkte, den klassischen Hype, auf den sich alle stürzen und zu dem es zig Vorträge gibt? Immerhin vier Mal war Graal(VM) (Teil-)Thema, was m.E. zeigt, dass das Thema “Performance” insbesondere im Bereich Startup-Zeiten und Speicherverbrauch in der Java-Welt wie schon vor fast 25 Jahren topaktuell bleibt. Ansonsten scheint jeder – auch ohne Vorliegen zwingender Gründe – sein Heil in der Microservice-Welt – gerne auch in der Cloud – zu suchen. Der Mobile- und IoT-Hype schein hingegen etwas abgeflacht zu sein.

Zum Vortrag “Cross-Platform Mobile-Apps mit Java – 2019” von Dr. Daniel Thommes, Geschäftsführer der NeverNull GmbH. Ein Technologieüberblick, durchaus ins Detail gehend, zur ewig aktuellen Frage “write once, run anywhere, und bitte plattformübergreifend single source und an jede Plattform optimal angepasst”. Daran ist bekanntlich schon Swing gescheitert. Und die Zeichen stehen ehrlich gesagt nicht besonders gut, dass es für so divergierende Anforderungen wie “App auf iOS, App auf Android, und Desktop auf Windows und Linux” jemals eine einheitliche und schöne Lösung geben könnte. Aber der Stand der Dinge hat mich schon etwas erschreckt, denn die “Lösungen” wie Transpiler, portierte Runtimes oder halbgare eigene UI-Toolkits sind doch eher ein Armutszeugnis. Es war ein Non-Sponsored-Talk, was den Vortragenden erkennbar in Gewissensprobleme stürzte und er den Eindruck, die eigene Lösung “MobileUI” seiner Firma zu sehr in den Vordergrund zu stellen, gewissenhaft zu vermeiden suchte. Tut so einem Vortrag nicht gut. Wobei man die Basics durchaus gut beherrscht, wie die “Java Forum Stuttgart”-App – natürlich auf relativ niedrigem Komplexitätsniveau, aber eine Lösung, die die einfachen Sachen sehr gut und sicher beherrscht ist ja auch wertvoll – zeigt. Ebenfalls fehlte erkennbar Tiefe für die diversen altbekannten Fallstricke der Cross-Plattform-UI-Entwicklung. Wenn etwas leidlich Komplexes in Demos nicht angeschnitten wird, liegt das erfahrungsgemäß daran, dass es nicht anständig funktioniert. Aber das ist natürlich nur wilde Spekulation kraft meiner 20-jährigen Erfahrung mit UI-Toolkits aller Art.

Michael Wiedeking war – fast selbstverständlich – auch wieder dabei, diesmal mit “Der eilige Graal” mit einer gewohnt locker präsentierten Reise durch die wunderbare Welt von JIT-Compilern, Hotspots, Bytecode und natürlich Graal. Der Vortrag krankte etwas an der unscharfen Abgrenzung zwischen Graal, GraalVM, Graal-JIT, Graal AOT und Graal native image-Feature. Für den Uneingeweihten ging es da manchmal zu sehr durcheinander, dank ausreichender Vorbildung konnte ich jedoch folgen. Aber selbst die Graal-Erfinder selbst tun sich da oft schwer, eben weil das Graal-Universum so viele Zielrichtungen verfolgt. Wie man das in 45 Minuten überhaupt vorstellen könnte – ich weiß es auch nicht.

Auch Stephan Frind versuchte sich unter dem Titel “Graal oder nicht Graal –ist das wirklich eine Frage?” an diesem Themenkomplex. Deutlich high-leveliger als Michael Wiedeking, und deshalb erfolgreicher beim Vermitteln des allgemeinen Überblicks. Leider aber dadurch eben auch fehlende Tiefe. Die Compilerbau-Details und die C1-C2-Abgrenzung in der JVM waren nicht immer ganz korrekt, aber das tat nicht weiter weh. Details, die nur einem passionierten i-Dipfeles-Scheißer auffallen. Die Abgrenzung Graal vs. GraalVM war auch hier eher unscharf, Schwächen der derzeitigen native image-Lösung kamen etwas zu kurz. Am Ende der Hinweis “Testen! GraalVM etwas schwieriger” könnte in der Kategorie “Untertreibung des Jahres” durchaus weit vorne landen. Man schaue sich die diversen Issues zum Thema native image auf GitHub an, um ein Gefühl für den präzisen Stand der Dinge bezüglich Stabilität und Umfang zu bekommen.

Aufgrund sparsamer Konkurrenz im Zeitslot verschlug es mich auch in “Aus eins mach zehn: Neuentwicklung mit Microservices” von Lars Alvincz und Bastian Feigl (andrena objects ag). Im Abstract war schon angekündigt, dass auch “Null vermeiden” ein Thema sein sollte, und das störte doch gewaltig – wie kann man etwa 10 Minuten der wertvollen Zeit auf so eine Nichtigkeit verschwenden, wo es doch um das Thema “wir lösen ein monolithisches System durch Microservices ab” ging? Mit all seinen interessanten Fragestellungen von “sind Microservices der richtige Ansatz” über “was hat die Neuentwicklung veranlasst” bis zu “das war der Ressourcenbedarf an CPU und Speicher vorher und nachher”. Neben den Klassikern “wie schneide ich die Services richtig” und “wie schaffe ich es, transaktionales DB-Verhalten mit dem Microservice-Umfeld zu verheiraten” natürlich. Es wurde nicht mal ansatzweise versucht, hier mehr als ein paar Worte zu den interessanten Teilgebieten zu verlieren. Besonders ärgerlich, weil die dargestellten Anforderungen von Kundenseite keineswegs eine Microservice-Architektur nahelegten oder gar erzwangen. Warum bei einer eher trivialen Standardanwendung, zudem mit zwingender Datenmigration und klarer Vorgabe an Funktionalität ein “Big Up Front-Design nicht sinnvoll/möglich” gewesen sein soll – behauptet wurde es, aber begründet nicht. Zusätzlich fiel mir wieder auf, dass die Lösung mit zwei Vortragenden nur ganz selten wirklich gut funktioniert. Auch die Themen “Versionsverwaltung des Codes” und “Trunk-based development vs. Feature Branches” wurde kurz gestreift, ohne dass klar wurde, warum das in diesem Kontext unbedingt Erwähnung finden musste. Insgesamt kein überzeugender Vortrag. In keinem der behandelten Unterthemen.

Da mein tägliches Brot immer noch Java 8 ist (böse Zungen würden behaupten, dass man in Java 8 natürlich immer noch genauso wie in Java 1.4 programmieren kann und schon die Einführung von Generics ein Fehler war) und ich die Neuerungen aus Java 9 bis Java 13 zwar kenne, aber nicht seriös im produktiven Einsatz habe, habe ich mir “Power Catch-up: Alles Praktische und Wichtige aus Java 9 bis 12” von Benjamin Schmid angeschaut. Sehr komprimiert, ohne Füllstoff, kompakt und doch mit Mehrwert gegenüber dem bloßen Lesen von Artikeln zum Thema. Ich war zufrieden. Auch wenn einzelne Themen wie die neuen GC-Varianten oder Project Loom natürlich in 45 Minuten nicht annähernd in gebührender Ausführlichkeit behandelt werden können. Der Vortragende hat sich aber mit potenziell verwirrenden Details angenehm zurückgehalten. Sehr gut.

Und das Beste kommt zum Schluss. “RxJava2: von 0 auf 100(?) in 45 Minuten” von und mit Johannes Schneider. Sensationell. Einer der besten Vorträgem egal zu welchem Thema, den ich je auf einer Konferenz gehört habe. Genau getimed auf die 45 Minuten, professionell und trotzdem humorvoll vorgetragen, voller Anregungen und Informationen. Didaktisch vom Feinsten. Ich muss gestehen, dass das den 2017er Vortrag zur reaktiven Programmierung, den ich anno dazumal gar nicht so schlecht fand, im Nachhinein ziemlich blass wirken lässt, Mein Kompliment an Johannes Schneider.

Was bleibt hängen? Zwei Dinge sollte man sich (ich mir) dringend im Detail anschauen: natürlich Graal(VM), und RxJava/ReactoveX, gerne auch im Swing-Kontext (auch wenn meine heimische Library natürlich die allermeisten Dinge schon auf anderem Wege erreicht – aber mit RxJava gäbe es hier die Möglichkeit, die Code-Lesbarkeit zu verbessern). Dafür kann man glatt seine Vorbehalte gegen 3rd-party-Abhängigkeiten über Bord werfen.

Eine Swing-Komponente – HexDumpView

 Java, Swing  Kommentare deaktiviert für Eine Swing-Komponente – HexDumpView
Sep 222018
 

Nach dem großen Erfolg der JVM-Memory-Indicator-Komponente und dem sensationellen Layout-Manager nun eine weitere unverzichtbare Komponente in jeder grafischen Oberfläche, die aus unverständlichen Gründen nicht schon standardmäßig in Swing ausgeliefert wird: die Anzeige eines klassischen Hexdumps. In der ebenso klassischen 8 + 16 + 16-Darstellung: 8-stellige Adresse, danach 16 Bytes in Hex-Darstellung, danach 16 ASCII-Zeichen als direkte Übersetzung der Bytes. Mit möglichst simpler API: hier ist das Byte-Array, mach’ mir die HexDump-View als Swing-Komponente dazu, die ich zur Nutzung nur noch in eine JScrollPane reinsetzen muss. Natürlich superschnell und superspeichersparend.

Testkandidat war eine 800KiB-Datei (ein adf-Floppyimage, aber das tut nichts zur Sache, hat aber mit dem Ursprung des Wunsches nach dieser Komponente zu tun – ich hoffe, da kann ich demnächst Vollzug melden, dann voraussichtlich nebenan im RISC OS-Blog), resultierend in einem rund 50000 (genau 51200) Zeilen umfassenden Hexdump.

Wie ist es implementiert? Da gab es natürlich eine Menge Varianten, die mir spontan in den Sinn kamen und die wie immer mit unterschiedlichen Kompromissen behaftet sind, die ich kurz anreißen will.

Variante 1: Erzeugen einer String-Repräsentation der Binärdaten und Nutzung einer “normalen” JTextArea oder JEditorPane. Geht schnell, ist unproblematisch in der Realisierung, alle typischen Dinge einer Textkomponente sind direkt verfügbar (auch wenn in diesem Falle das Copy&Paste vermutlich nicht ganz das ist, was der Anwender erwartet). Aber speicher- und laufzeittechnisch sicher suboptimal – die Konvertierung in eine lange Zeichenkette, obwohl man diese ja bei größeren Datensätzen niemals gleichzeitig auf dem Schirm sieht, ist doch verhältnismäßig teuer, und die Wahrscheinlichkeit, dass sowohl das Quell-Byte-Array als auch die Zeichenkette im Speicher verbleiben müssen, ist ja doch recht groß (vermutlich will der Verwender seine Quelldaten ja nicht wegwerfen, sondern noch weiter verarbeiten), und pro Byte kostet die String-Repräsentation ja zusätzlich etwa 14 Bytes (73 Zeichen pro 16 Bytes.

Variante 1a: Erzeugen einer String-Repräsentation der Binärdaten und wrappen in ein java.swing.text.Document als Unterfütterung einer JTextArea/JEditorPane. Spontan würde ich denken, dass so ein reines ReadOnly-Document etwas besser bei Speicher- und Laufzeitverhalten ist als ein Wald- und Wiesen-Document aus dem Swing-Bauchladen.

Variante 2: Erzeugen einer String-Repräsentation der Binärdaten und direkter Redraw in paintComponent. Habe ich getestet, für mäßig große Binärdaten funktioniert das erschreckenderweise recht gut (in meinem Falle waren das rund 50000 Zeilen des Hexdumps), was glaube ich eher zeigt, wie schnell die Maschinen heutzutage sind, und nicht, wie man ein solches Problem angehen sollte. Speichertechnisch ist das etwas günstiger als eine JTextArea oder JEditorPane weil man die ganze unnütze Funktionalität weglässt, aber das Hauptproblem der Erzeugung der großen Zeichenkette bleibt.

Variante 2a: wie 2, aber mit optimiertem Redraw (paintComponent) – nur die Zeilen zeichnen, die laut Clipping Rectangle tatsächlich gezeichnet werden. Das ist geschwindigkeitstechnisch schon erheblich besser als Variante 2, weil eben nur sagen wir 100 Zeilen gezeichnet werden statt 50000, und deshalb aus der vollständigen String-Repräsentation auch nur 100 Substring-Operationen anstatt 50000 durchgeführt werden müssen. Ich habe allerdings nicht analysiert, ob denn den Substring-Operationen im Vergleich zum dann weggeclippten drawString wirklich die teurere Operation ist – einigen wir uns auf “hilft beides nicht”.

Variante 3: kein Erzeugen der vollständigen String-Repräsentation, sondern Erzeugung der zu malenden Textzeile “on demand” aus den Quelldaten, dem Byte-Array. Dabei Inspektion des Clipping Rectangles, um nur die notwendigen Operationen durchzuführen.

Nachfolgend beispielhaft die Implementierungsvariante 3 (die anderen sind zu trivial :-)), als ganz nacktes Java – keine externen Abhängigkeiten, und vermutlich würde der Code so schon unter Java 1.1 mit Swing 1.1 funktionieren. Es gibt hier natürlich Lücken (es sind nur rund 200 Zeilen Code, für eine vollständige JComponent, inklusive JavaDoc!), die selbstverständlich nur als Aufforderung zur Übung an den Leser zu verstehen sind. Encoding-Unterstützung für den Textbereich, Implementierung eines Carets und einer Selektionsmöglichkeit, Unterstützung für nicht-Monospaced-Schriften, on-demand-loading der Binärdaten aus einer Datei größer als MaxHeap während der Benutzer scrollt, suchen nach Text und Bytes und Adressen, Darstellung in Byte- und Word-Form, hinzufügen der Zeilennummer…

Auch eine Variante 4 – ein Document das allein durch die Quelldaten unterfüttert ist und on-the-fly den Text für die JTextComponent generiert, wäre eine interessante Forschungsaufgabe.

/*
 * (c) hubersn Software
 * www.hubersn.com
 *
 * Use wherever you like, change whatever you want. It's free!
 */
package com.hubersn.playground.swing;

import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;

import javax.swing.JComponent;

/**
 * Component showing a hexdump.
 */
public class HexDumpView extends JComponent {

  /** Number of characters in one line of a hexdump: 8 characters address, 16 2-character-bytes, 16 characters, spaces. */
  private static final int LINE_LENGTH = 8 + 1 + 3 * 16 + 16;

  private byte[] data;

  private int lineCount = -1;

  private FontMetrics fm = null;

  private int lineHeight = 0;

  private int lineAscent = 0;

  private int characterWidth;

  private int calculatedWidth;

  private int calculatedHeight;

  /**
   * Creates a hexdump view based on the given data with a monospaced 12pt font.
   *
   * @param data hexdump data.
   */
  public HexDumpView(final byte[] data) {
    setFont(new Font("Monospaced", Font.PLAIN, 12));
    setData(data);
  }

  /**
   * Sets the data to be visualized by this hexdump view.
   *
   * @param data hexdump data.
   */
  public void setData(final byte[] data) {
    this.data = data;
    refresh();
  }

  /**
   * Sets the font to be used for the hexdump - note that only a monospaced font will yield sensible results.
   */
  @Override
  public void setFont(final Font font) {
    super.setFont(font);
    this.fm = null;
    refresh();
  }

  private void refresh() {
    if (this.data == null) {
      return;
    }
    calculateSizes();
    revalidate();
  }

  private int calculateLineCount() {
    if (this.data == null) {
      return 0;
    }
    int lc = this.data.length / 16;
    if (this.data.length % 16 > 0) {
      lc++;
    }
    return lc;
  }

  private void calculateSizes() {
    if (this.fm == null) {
      this.fm = getFontMetrics(getFont());
      this.lineHeight = this.fm.getHeight();
      this.lineAscent = this.fm.getAscent();
      this.characterWidth = this.fm.stringWidth("M");
    }
    this.lineCount = calculateLineCount();
    this.calculatedWidth = getInsets().left + getInsets().right;
    this.calculatedWidth += LINE_LENGTH * this.characterWidth;
    this.calculatedHeight = getInsets().top + getInsets().bottom;
    this.calculatedHeight += this.lineCount * this.lineHeight + this.lineAscent;
  }

  @Override
  public Dimension getPreferredSize() {
    return new Dimension(this.calculatedWidth, this.calculatedHeight);
  }

  @Override
  protected void paintComponent(Graphics g) {
    super.paintComponent(g);
    if (this.data == null) {
      return;
    }
    Rectangle clipBounds = g.getClipBounds();
    Insets insets = getInsets();
    int y = insets.top + this.lineAscent;
    for (int i = 0; i < this.lineCount; i++) {
      // optimized redraw - check if clipping bounds contains line
      if (shouldDraw(clipBounds, y)) {
        String str = getHexDumpLine(this.data, 0, i * 16);
        g.drawString(str, insets.left, y);
      }
      y += this.lineHeight;
    }
  }

  private boolean shouldDraw(Rectangle r, int y) {
    return !(y + this.lineHeight < r.y || y - this.lineHeight > r.y + r.height);
  }

  //
  // -- utility methods for hex(dump) representation
  //

  /**
   * Converts a value between 0 and 15 into the corresponding hex character - throws an
   * IllegalArgumentException on illegal input values.
   *
   * @param value value to convert.
   * @param useUpperCase use upper case letters A-F?
   * @return hex character representing value.
   */
  private static char valueToHexChar(final long value) {
    if (value < 0 || value > 15) {
      throw new IllegalArgumentException("Value " + value + " out of range - must be 0-15");
    }
    if (value < 10) return (char) ('0' + value);

    return (char) ('A' + (value - 10));
  }

  /**
   * Converts the given value to a number of hex characters specified in nibble count value.
   *
   * @param value
   * @param nibbleCount amount of nibbles (characters) to return.
   * @param useUpperCase use upper case hex characters.
   * @return hex representation of value.
   */
  private static String valueToHexChars(final long value, final int nibbleCount) {
    char[] hexChars = new char[nibbleCount];
    for (int i = 0; i < nibbleCount; i++) {
      hexChars[i] = valueToHexChar(0b1111 & (value >> (4 * (nibbleCount - 1 - i))));
    }
    return new String(hexChars);
  }

  /**
   * Returns a line (not terminated) for a hexdump output - either 16 bytes starting from offset or less if data has less than 16 bytes available.
   *
   * @param data data for dump.
   * @param startAddress start address of data.
   * @param offset offset into data.
   * @return hexdump line.
   */
  private static String getHexDumpLine(final byte[] data, final long startAddress, final int offset) {
    StringBuilder sb = new StringBuilder(LINE_LENGTH);
    sb.append(valueToHexChars(startAddress + offset, 8));
    sb.append(' ');
    for (int lineOffset = 0; lineOffset < 16; lineOffset++) {
      if (offset + lineOffset < data.length) {
        sb.append(valueToHexChars(getUnsigned(data[offset + lineOffset]), 2));
        sb.append(' ');
      } else {
        sb.append("   ");
      }
    }
    for (int lineOffset = 0; lineOffset < 16; lineOffset++) {
      if (offset + lineOffset < data.length) {
        sb.append(getPrintableCharacter(getUnsigned(data[offset + lineOffset])));
      }
    }
    return sb.toString();
  }

  private static int getUnsigned(final byte b) {
    if (b < 0) {
      return ((int) b + 256);
    }
    return b;
  }

  private static char getPrintableCharacter(final int byteValue) {
    if (byteValue < 32 || byteValue > 255 || (byteValue > 128 && byteValue < 160)) {
      return '.';
    }
    return (char) byteValue;
  }

}

Bits frickeln mit Java

 Java, OS, Software  Kommentare deaktiviert für Bits frickeln mit Java
Aug 072018
 

Gerade bin ich dabei – zum besseren Kennenlernen von Filecore-basierten Dateisystemen (man muss den Feind kennen) – in Java ein kleines Tool zu entwickeln, das Disc-Images im Filecore-Format versteht und Dateien und Verzeichnisse daraus extrahieren kann. Nur lesend, um weitere Komplexitäten erst mal zu vermeiden.

Etwas Hintergrund: Filecore ist das native Dateisystem unter RISC OS. Oft wird es auch als “ADFS” bezeichnet, weil das “Advanced Disc Filing System” quasi die erste Implementierung von Filecore war (damals, Floppy-Zeit, zur Archimedes-Ära 800 KiB auf einer 3,5″-DD-Diskette), bevor es abstrahiert und generalisiert als Basis für Disketten- und Festplattenformate in RISC OS Einzug hielt. Es gab über die Jahre reichlich Varianten, um die diversen Einschränkungen und Limits in den besser nutzbaren Bereich zu schieben – das E-Format ist das Übliche für DD-Disketten (800 KiB) und man findet es heute in den Weiten des Internets als .adf-Disc-Images der klassischen Archimedes-Software. Später kam das F-Format für die HD-Floppies (1600 KiB). Schließlich wurde mit RISC OS 4 das E+/F+-Format eingeführt (“Big Map”), um die lächerlich niedrige Grenze solcher Dinge wie “maximale Anzahl von Einträgen in einem Verzeichnis” auf ein halbwegs erträgliches Niveau zu heben.

Wie auch immer – beim Hantieren mit Emulatoren und natürlich dem MIST(er) hat man häufig mit diesen .adf- (Floppy-Images) und .hdf- (Harddisc-Images) Dateien zu tun. Oftmals will man “nur kurz” einen Blick reinwerfen und dazu nicht gleich den Emulator hochfahren. Zumal die Emulatoren auch nicht alle so richtig nutzerfreundlich sind was das “Mounten” des Images angeht.

Also: ein Tool muss her. Systemunabhängig und natürlich mit anständigem UI. Also Java. Das Problem: Filecore wurde von echten Bitfuchsern entwickelt. Da wimmelt es nur so von Bitleisten, unsigned values mit allem zwischen 8 und 64 bit und sonstigem, was unter Java nicht so richtig “nativ” unterstützt wird. Übrigens gibt es Spezialisten, die es auch noch gut finden, dass Java keine “unsigned types” hat – für mich ist dieser Artikel ein Beispiel dafür, wie eine limitierte Erfahrung auf nur wenige Programmiersprachen den Blick aufs wesentliche vernebeln kann, denn die allermeisten der genannten Probleme sind nur inhärent im Java- und C-Typsystem, aber in vernünftigen Sprachen wie Ada elegant gelöst. Dumm, dass die Java-Erfinder vermutlich einen genauso eingeschränkten Blickwinkel hatten – C, C++, Smalltalk, Ende der Geschichte. Aber ich schweife ab.

Jedenfalls habe ich nach Bibliotheken gesucht, die unter Java die Bitfrickelei und unsigned types im Handling etwas angenehmer machen. Leider bin ich bis auf Lukas Eders jOOU-Bibliothek auf nix gescheites gestoßen. Klar, im Apache-Commons-Universum gibt es ein paar Dinge, in Guava ebenso, und sogar Oracle hat das Licht gesehen und ein paar nützliche Dinge in Java 8 nachgerüstet. Also: selbst ist der Mann. “Not invented here” ist schließlich eine der grundsätzlichen Triebfedern der IT.

Es sei denn, jemand hat einen Hinweis auf eine vernünftige, umfassende Bibliothek um meine Bedürfnisse zu erfüllen. Unter ebenso vernünftiger Open-Source-Lizenz natürlich. UAwg! Nebenbei: die eigentliche Implementierung ist nun nicht das große Problem, das Finden geeigneter Doku und fehlerarme Interpretation derselben nebst Erkennen gewünschter und unerwünschter Abweichungen davon in der Praxis hingegen schon. Hat man das Problem erst mal in einer Sprache geknackt, sollte eine Portierung in andere Sprachen kein großes Problem sein. Es wäre eine interessante Fallstudie, den Ada-Code neben dem Java-Code zu sehen.

Und falls jemand ein anständiges (nicht jedoch komplexes!) Dateisystem kennt mit einer simplen Implementierung in einer freien Lizenz – mail me. Filecore hat einen Nachfolger schon seit etwa 30 Jahren verdient.

Oracle und Java – hü, hott, oder was ganz anderes?

 Java  Kommentare deaktiviert für Oracle und Java – hü, hott, oder was ganz anderes?
Mrz 292018
 

Oracle hat die “Roadmap” für Java 11 verkündet (hier und hier). JavaFX fliegt aus Java SE raus. Das Java-Plugin und damit nicht nur das Applet, sondern auch Java Web Start (und damit ironischerweise das, was Oracle bei Abkündigung des Applets noch als Ersatzlösung propagiert hat, auch wenn jeder wusste, dass es nur eine Deployment-Lösung ist und keine Browserintegrationslösung), fliegen raus.

OK, kann man so machen. Es gibt sicher gute Gründe dafür (im Gegensatz zur Entscheidung “wir laden .properties-Dateien ab sofort immer UTF-8”, für die ich bis heute keine auch nur annähernd stichhaltige Begründung gefunden habe – auch warum JAXB nun plötzlich aus Java SE raus musste, man weiß es nicht). Aber es ist ein Rückwärtskompatibilitätsproblem. Und es ist Wasser auf die Mühlen der “Java auf dem Client ist tot”-Fraktion, obwohl es bis heute keine WORA-Alternative für grafische Oberflächen gibt. Und eine weitere Kehrtwende im scheinbar ewigen Auf und Ab, beginnend mit der Idee des AWT in Java 1.0.

Das hin- und her-Geeiere gab es ja auch schon beim Thema “Java-Datenbank” – irgendwann wurde recht überraschend die Apache Derby als “Java DB” ins JDK aufgenommen, dann hat man diese Version – mit Hinweis auf die angeblich gefährdete Rückwärtskompatibilität – nicht regelmäßig aktualisiert, um sie schließlich wieder komplett zu entfernen (Java 9).

Auch die JavaFX-Geschichte ist ja eher wechselvoll. Zuerst der Flash-/ActionScript-/Silverlight-/Flex-Konkurrent mit dieser merkwürdigen Skriptsprache, dann JavaFX 2 mit der (durchaus vernünftigen) Idee der Ablösetechnologie für Swing, ohne aber die Grundlagen einer modernen UI gleich mitzuliefern (Accessibility, Comboboxen…dafür waren Charts dabei…). Dann die Integration ins JRE und die Bestrebung, JavaFX auch auf Android und iOS zu bringen. Reichlich Manpower und – für Java-Verhältnisse – zügiges Bugfixing. Dann Abbau der Manpower, Absage an die Mobilstrategie, nun Rauswurf aus Java SE und Übergabe an (oder vielmehr Hoffnung auf Übernahme durch) die Community.

Nun ist es ja durchaus diskussionswürdig, was in den “Standardlieferumfang” gehören soll und was nicht. Und durch Project Jigsaw hatte man nun ja eigentlich die Möglichkeit, die “modular JRE” Wirklichkeit werden zu lassen. Warum also nicht einen “Kern” definieren und den bisherigen Lieferumfang “on demand” dazupacken? Man versteht es nicht.

Auch “Jakarta EE” als “community driven effort” und JavaEE-Weiterführung steht ja noch nicht wirklich in voller Blüte. Es könnte so enden wie bei Hudson und OpenOffice. Oder Glassfish. Und wie geht es eigentlich Netbeans und VisualVM?

Ist das jetzige Gewurschtel eine notwendige Konsequenz aus anderen Entscheidungen wie der erhöhten Release-Frequenz? Müsste man denn dann nicht logischerweise den Änderungsumfang eher kleiner halten – nur, weil man was entfernt, löst man doch kein Problem. Dafür schafft man massiv Unsicherheit bezüglich des Commitments von Oracle zur Langzeitstabilität der Java-Plattform. Und was spricht letztlich für Java wenn nicht das bisherige Kompatibilitätsversprechen? Versucht man, durch hektische Betriebsamkeit Dynamik vorzutäuschen?

Es bleibt spannend. Enttäuschend finde ich die an den Haaren herbeigezogenen Begründungen vor allem für das Ende von Java Web Start. Gerade Oracle müsste doch wissen, dass die IT aus mehr als “Apps aus dem App-Store” besteht, und dass die Voraussetzung “ich habe Java auf dem Client installiert” für typische Firmen-IT jetzt nicht gerade das große Hindernis ist – andere Software hat auch Abhängigkeiten, die separat installiert werden müssen (.NET anyone?). Oder will man demnächst die Oracle-Datenbank abkündigen, weil sie nicht auf Android läuft? Interessant auch die Aussage, dass alle Web Start-basierten Oracle-Produkte für immer auf Java 8 bleiben werden. Das klingt nach echtem Zukunftsplan.

Einzig positiv aus meiner Sicht: der Markt ist nun weit offen für eine andere, bessere Deployment-Art – Java Web Start hatte ja auch seine Macken. Vielleicht ist “Zero Install” vom alten RISC OS-Spezl Thomas Leonard (berühmt für den ROX-Filer und ROX Desktop) ja die Alternative, die sich durchsetzt.

Ein Swing-Layout-Manager – OneRowSameSizeLayoutManager

 Java, Swing  Kommentare deaktiviert für Ein Swing-Layout-Manager – OneRowSameSizeLayoutManager
Feb 262018
 

Jeder Java-Entwickler, der schon mal seriös mit Swing eine Oberfläche gebaut hat, hat schon einmal mit dem Layout-Manager seiner Wahl gekämpft. Die standardmäßig im JDK ausgelieferten Layout-Manager sind eher gewöhnungsbedürftig bis leistungsschwach, mindestens aber umständlich. GroupLayout für komplexe Dinge und BorderLayout für Basislayouts sind gerade noch so verwendbar. Beliebte 3rd-party-Layout-Manager sind sicherlich MigLayout (oder MiG Layout? Man kann sich nicht entscheiden…), FormLayout, TableLayout und DesignGridLayout (Jahre nach dem Ableben von java.net wohl nur noch über Maven Central beziehbar).

Für besondere Bedürfnisse ist es manchmal eine gute Idee, einen einfachen LayoutManager selbst zu bauen. Ich habe das mal beispielhaft getan für den Anwendungsfall “Layout einer typischen Reihe von Buttons unten in einem Dialog”. Im Gegensatz zum merkwürdigen Beispiel im Swing-Tutorial von Oracle namens “DiagonalLayout” hoffe ich jedenfalls, dass mein Beispiel etwas mehr Nützlichkeit ausstrahlt. Der Quellcode ist mehr auf Lesbarkeit und Nachvollziehbarkeit denn auf Eleganz getrimmt. An einem schmissigeren Namen arbeite ich noch…

Irgendwann werde ich noch mal den “Packer”, einen Layout-Manager von Tcl/Tk, für Swing nachbauen. M.E. war der “Packer” in seinem Verhalten und der Vorhersagbarkeit des programmierten Resultats absolut untadelig, etwas was ich von den meisten Swing-LayoutManagern nicht sagen kann. Aber vielleicht ist meine Erinnerung an den Packer auch rosarot verklärt – ist schon über zwei Jahrzehnte her seit meinem ersten und letzten Kontakt.

/*
 * (c) hubersn Software
 * www.hubersn.com
 */
package com.hubersn.playground.swing.layout;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager;

import javax.swing.SwingConstants;

/**
 * A layout manager supporting a row of components kept at the same width and height with specifiable alignment and gap size between the
 * components. It has its own insets to avoid having to work with empty borders.
 */
public class OneRowSameSizeLayout implements LayoutManager {

  /** Constant for default left alignment (identical to SwingConstants.LEFT). */
  public static final int ALIGN_LEFT = SwingConstants.LEFT;
  /** Constant for default centre alignment (identical to SwingConstants.CENTER). */
  public static final int ALIGN_CENTER = SwingConstants.CENTER;
  /** Constant for default right alignment (identical to SwingConstants.RIGHT). */
  public static final int ALIGN_RIGHT = SwingConstants.RIGHT;

  // calculated overall minimum/preferred size
  private int minWidth = 0;
  private int minHeight = 0;
  private int preferredWidth = 0;
  private int preferredHeight = 0;
  // calculated maximum component preferred sizes
  private int maxPreferredWidth = 0;
  private int maxPreferredHeight = 0;

  private Insets insets;

  private int componentGap = 5;

  private int alignment;

  /**
   * Creates a new layout with right alignment, 4 pixels gap and 8 pixels insets all around.
   */
  public OneRowSameSizeLayout() {
    this(ALIGN_RIGHT, 4, new Insets(8, 8, 8, 8));
  }

  /**
   * Creates a new layout with given alignment, gap between components and insets.
   * 
   * @param alignment component alignment - ALIGN_LEFT, ALIGN_CENTER or ALIGN_RIGHT.
   * @param componentGap gap in pixels between components.
   * @param insets insets to use additional to possible container border - if null, all insets are 0.
   */
  public OneRowSameSizeLayout(final int alignment, final int componentGap, final Insets insets) {
    setAlignment(alignment);
    this.componentGap = componentGap;
    this.insets = insets;
    if (insets == null) {
      this.insets = new Insets(0, 0, 0, 0);
    }
  }

  /**
   * Sets the component alignment for this layout.
   * 
   * @param alignment component alignment - ALIGN_LEFT, ALIGN_CENTER or ALIGN_RIGHT.
   */
  public void setAlignment(final int alignment) {
    this.alignment = alignment;
  }

  @Override
  public void addLayoutComponent(String name, Component comp) {
    // nothing to do - we are not interested in user-given layout constraints.
  }

  @Override
  public void removeLayoutComponent(Component comp) {
    // nothing to do.
  }

  private void calculateSizes(Container parent) {
    final int numberOfComponents = parent.getComponentCount();

    this.preferredWidth = this.insets.left + this.insets.right;
    this.preferredHeight = this.insets.top + this.insets.bottom;
    this.minWidth = 0;
    this.minHeight = 0;

    final int numberOfVisibleComponents = getNumberOfVisibleComponents(parent);
    for (int i = 0; i < numberOfComponents; i++) {
      final Component c = parent.getComponent(i);
      if (c.isVisible()) {
        final Dimension d = c.getPreferredSize();
        this.maxPreferredWidth = Math.max(this.maxPreferredWidth, d.width);
        this.maxPreferredHeight = Math.max(this.maxPreferredHeight, d.height);
        this.minWidth += c.getMinimumSize().width;
      }
    }

    this.preferredHeight += this.maxPreferredHeight;
    this.minHeight = this.maxPreferredHeight;

    if (numberOfVisibleComponents > 0) {
      this.preferredWidth += (this.maxPreferredWidth * numberOfVisibleComponents);
      this.preferredWidth += this.componentGap * (numberOfVisibleComponents - 1);
    }
  }

  @Override
  public Dimension preferredLayoutSize(Container parent) {
    final Dimension dim = new Dimension(0, 0);

    calculateSizes(parent);

    final Insets parentInsets = parent.getInsets();
    dim.width = this.preferredWidth + parentInsets.left + parentInsets.right;
    dim.height = this.preferredHeight + parentInsets.top + parentInsets.bottom;

    return dim;
  }

  @Override
  public Dimension minimumLayoutSize(Container parent) {
    final Dimension dim = new Dimension(0, 0);

    final Insets parentInsets = parent.getInsets();
    dim.width = this.minWidth + parentInsets.left + parentInsets.right;
    dim.height = this.minHeight + parentInsets.top + parentInsets.bottom;

    return dim;
  }

  @Override
  public void layoutContainer(Container parent) {
    final int numberOfVisibleComponents = getNumberOfVisibleComponents(parent);
    if (numberOfVisibleComponents == 0) {
      return;
    }

    calculateSizes(parent);

    final Insets parentInsets = parent.getInsets();

    // will all fit into parent? Which sizes to reduce?
    // width sizing strategy:
    // first, shrink gap and insets to 0
    // then, shrink button width and height
    // height sizing strategy:
    // shrink insets to 0
    final int parentWidth = parent.getWidth() - (parentInsets.left + parentInsets.right);
    final int parentHeight = parent.getHeight() - (parentInsets.top + parentInsets.bottom);
    final int heightToUse = Math.min(parentHeight, this.maxPreferredHeight);
    // width shrinking - toUse values are used for final layout bounds setting step
    int widthToUse = this.maxPreferredWidth;
    int buttonGapToUse = this.componentGap;
    int leftInsetToUse = this.insets.left;
    int rightInsetToUse = this.insets.right;

    int amountToReduceWidth = this.preferredWidth - parentWidth;
    if (amountToReduceWidth > 0) {
      // we need to save some pixels...
      // check for left/right insets
      if (leftInsetToUse + rightInsetToUse >= amountToReduceWidth) {
        int reduceInsetBy = amountToReduceWidth / 2;
        leftInsetToUse -= reduceInsetBy;
        amountToReduceWidth = 0;
      } else {
        leftInsetToUse = 0;
        amountToReduceWidth -= (this.insets.left + this.insets.right);
      }
      if (amountToReduceWidth > 0) {
        // check for button gaps
        if (numberOfVisibleComponents > 1 && (numberOfVisibleComponents - 1) * buttonGapToUse >= amountToReduceWidth) {
          buttonGapToUse -= amountToReduceWidth / (numberOfVisibleComponents - 1);
          amountToReduceWidth = 0;
        } else {
          buttonGapToUse = 0;
          amountToReduceWidth -= (numberOfVisibleComponents - 1) * this.componentGap;
        }
        // now shrink the button size itself as a last resort
        if (amountToReduceWidth > 0) {
          widthToUse = parentWidth / numberOfVisibleComponents;
        }
      }
    }

    // height shrinking - toUse values are used for final layout bounds setting step
    int topInsetToUse = this.insets.top;
    int amountToReduceHeight = this.preferredHeight - parentHeight;
    if (amountToReduceHeight > 0) {
      // we need to save some pixels...
      // check for top/bottom insets
      if (topInsetToUse + this.insets.bottom >= amountToReduceHeight) {
        int reduceInsetBy = amountToReduceHeight / 2;
        topInsetToUse -= reduceInsetBy;
      } else {
        topInsetToUse = 0;
      }
    }

    // finally size and place the components
    int x = parentInsets.left + leftInsetToUse;
    // alignment is only relevant if we have enough space
    if (parentWidth > this.preferredWidth) {
      if (this.alignment == ALIGN_CENTER) {
        x += (parentWidth - this.preferredWidth) / 2;
      } else if (this.alignment == ALIGN_RIGHT) {
        x += parentWidth - this.preferredWidth;
      }
    }

    final int y = parentInsets.top + topInsetToUse;

    final int numberOfComponents = parent.getComponentCount();
    for (int i = 0; i < numberOfComponents; i++) {
      Component c = parent.getComponent(i);
      if (c.isVisible()) {
        c.setBounds(x, y, widthToUse, heightToUse);
        x += widthToUse + buttonGapToUse;
      }
    }
  }

  private static int getNumberOfVisibleComponents(final Container parent) {
    final int numberOfComponents = parent.getComponentCount();
    int numberOfVisibleComponents = 0;

    for (int i = 0; i < numberOfComponents; i++) {
      Component c = parent.getComponent(i);
      if (c.isVisible()) {
        numberOfVisibleComponents++;
      }
    }

    return numberOfVisibleComponents;
  }
}

Eine Swing-Komponente – JVM-Memory-Indicator

 Java, Swing  Kommentare deaktiviert für Eine Swing-Komponente – JVM-Memory-Indicator
Feb 242018
 

Ich entwickle grafische Oberflächen in Java Swing seit der Zeit, als man Swing noch separat herunterladen musste und es noch im Package com.sun.java.swing wohnte. JDK 1.1. In JDK 1.0.2 hat man sich kurz AWT angeschaut, gelacht und es zu den Akten gelegt. Swing hingegen war ein seriöser Versuch eines leistungsfähigen und trotzdem plattformübergreifenden UI-Toolkits.

Drei Dinge jenseits von Java SE braucht der Swing-Entwickler bis heute: Komponenten, Layout-Manager und ggf. ein Application Framework. Neulich war ich auf der Suche nach einer kleinen schmalen Komponente für eins meiner zahlreichen Swing-basierten Inhouse-Tools, um den Zustand der JVM-Speicherverwaltung zu signalisieren, ähnlich wie Eclipse es standardmäßig anbietet wenn man es aktiviert unter “Window -> Preferences -> Global -> Show heap status”.

Als erfahrener Googler hätte ich erwartet, eher ein Problem mit zuviel als zuwenig Auswahl zu haben. Aber Pustekuchen: letztlich fand ich nur eine Frage auf StackOverflow (witzigerweise wenige Stunden nach meiner Antwort als off-topic geschlossen – und das bei einer Frage von 2009!) nach einer solchen Komponente, die lediglich zwei komplett falsche Antworten der Kategorie “Thema verfehlt” nach sich zog. Also habe ich mir mal eine Stunde Zeit genommen, um mit minimalem Aufwand eine solche Komponente zu bauen. Als Basis habe ich JProgressBar verwendet. Das Ergebnis ist natürlich weit weg von der wünschenswerten Flexibilität einer solchen Komponente, wenn sie in unterschiedlichen Kontexten wiederverwendbar sein soll, aber als ein schönes einfaches Beispiel taugt es dennoch. Die Komponente ist lizenztechnisch frei verwendbar allüberall. Eigene Anpassungen ausdrücklich erwünscht. i18n-Unterstützung, MiB vs. MB, Text und Tooltip über extern vorgebbare Texte mit Platzhaltern realisieren, ein “Trash”-Icon zum Auslösen des GC anstatt der nichtoffensichtlichen Doppelclick-Variante, eine “Mark”-Funktionalität ähnlich Eclipse – die Erweiterungsideen sind beinahe endlos.

/*
 * (c) hubersn Software
 * www.hubersn.com
 * 
 * Use wherever you like, change whatever you want. It's free!
 */
package com.hubersn.playground.swing;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import javax.swing.JProgressBar;
import javax.swing.Timer;

/**
 * JVM Memory Indicator based on JProgressBar.
 */
public class ProgressBarBasedJVMMemoryIndicator extends JProgressBar {

  private static final long serialVersionUID = 1L;

  private static final int TIMER_INTERVAL_IN_MS = 1000;

  /**
   * Creates a new instance of ProgressBarBasedJVMMemoryIndicator.
   */
  public ProgressBarBasedJVMMemoryIndicator() {
    super(0, 100);
    setStringPainted(true);
    setString("");
    addMouseListener(new MouseAdapter() {
      @Override
      public void mouseClicked(final MouseEvent mev) {
        if (mev.getClickCount() == 2) {
          System.gc();
          update();
        }
      }
    });
    final Timer t = new Timer(TIMER_INTERVAL_IN_MS, new ActionListener() {
      @Override
      public void actionPerformed(final ActionEvent aev) {
        update();
      }
    });
    t.start();
    update();
  }

  private void update() {
    final Runtime jvmRuntime = Runtime.getRuntime();
    final long totalMemory = jvmRuntime.totalMemory();
    final long maxMemory = jvmRuntime.maxMemory();
    final long usedMemory = totalMemory - jvmRuntime.freeMemory();

    final long MEBIBYTE_FACTOR = 1024 * 1024;
    final long totalMemoryInMebibytes = totalMemory / MEBIBYTE_FACTOR;
    final long maxMemoryInMebibytes = maxMemory / MEBIBYTE_FACTOR;
    final long usedMemoryInMebibytes = usedMemory / MEBIBYTE_FACTOR;
    final int usedPercentage = (int) ((100 * usedMemory) / totalMemory);
    final String textToShow = usedMemoryInMebibytes + "MiB of " + totalMemoryInMebibytes + "MiB";
    final String toolTipToShow = "Heap size: " + usedMemoryInMebibytes + "MiB of total: " + totalMemoryInMebibytes + "MiB max: "
        + maxMemoryInMebibytes + "MiB - double-click to run Garbage Collector";

    setValue(usedPercentage);
    setString(textToShow);
    setToolTipText(toolTipToShow);
  }
}

Aus mir unerfindlichen Gründen habe ich danach angefangen, eine superflexible, i18n-fähige, effiziente, JComponent-basierte Variante davon zu bauen. Hat auch nicht viel länger gedauert. Soll “demnächst” im Rahmen meiner ultimativen Swing-Bibliothek das Licht der Welt erblicken – das soll diese Bibliothek aber schon seit ungefähr 2001…

Oracle und VisualVM

 Java  Kommentare deaktiviert für Oracle und VisualVM
Dez 302017
 

Eines meiner Lieblings-Java-Tools ist die VisualVM – vor wenigen Tagen wurde Version 1.4 released, mit offiziellem Java 9-Support. Die Vorgänger-Version 1.3.9 funktionierte schon leidlich mit Java 9, aber “offiziell” ist bekanntlich immer besser.

Und das führt uns zum traurigen Teil der Geschichte, nämlich Oracle’s Entscheidung, JDK 9 ohne die VisualVM auszuliefern (neben der grandiosen Idee, die bisher per Definition in ISO-8859-1 vorliegenden .properties-Dateien einfach mal in UTF-8 zu lesen, sicher die schlechteste). Und das ohne vernünftigen Ersatz – Java Mission Control hat bekanntlich eine üble Lizenzeinschränkung bezüglich kommerziellem Einsatz, und die gute alte JConsole ist eben genau das – alt. Mit VisualVM war es schön einfach, selbst unbedarfte User “mal schnell” einen Heapdump oder Threaddump ziehen zu lassen. Vorbei.

Immerhin ist die Version 1.4 ein Lebenszeichen, ich hatte schon Schlimmeres befürchtet. Der Hinweis, dass 1.4 nun auf der NetBeans-Plattform Version 9 basiert (wenn auch einer Entwicklungsversion), ist gleichzeitig auch ein Lebenszeichen von NetBeans, das bekanntlich vor einiger Zeit von Oracle zu Apache gewechselt ist – seither hoffen alle, dass das besser läuft als bei OpenOffice. NetBeans 8.2 war ja von Oktober 2016, seither waren die News eher sparsam. Ich bin zar nie mit NetBeans warm geworden, aber etwas Konkurrenz tut sowohl Eclipse als auch IntelliJ immer gut.

Amüsantes Detail aus den Release Notes von VisualVM 1.4: Windows 10 ist offenbar keine unterstützte Plattform…

Java Forum Stuttgart 2017

 Java  Kommentare deaktiviert für Java Forum Stuttgart 2017
Jul 072017
 

Das Java Forum Stuttgart fand 2017 zum nunmehr zwanzigsten Male statt, und ich war mal wieder vor Ort dabei, da das Vortragsprogramm aus meiner Sicht recht ansprechend war. Ich nutze das Java Forum hauptsächlich, um mal wieder dran erinnert zu werden, wie riesig das Java-Universum ist und wie viele Technologien und Frameworks es da gibt, Hype inklusive. Es gab zum Abschluss als Pecha-Kucha-Vortrag einen Rückblick auf die letzten 19 Veranstaltungen inklusive Blick auf die damals aktuellen Themen, da war einiges an damals hippem Zeugs dabei, das schon wenige Jahre später komplett in der Versenkung verschwunden war.

Aber das Thema soll ja weniger die Vergangenheit als die Gegenwart sein.

Los ging’s mit der JAX-RS 2.1-Schnellbleiche durch Markus Karg, der in der Expert Group des dazugehörigen JSR 370 mitarbeitet. Meine Expertise in diesem Bereich ist sehr begrenzt, aber ich konnte dem Vortrag inhaltlich sehr gut folgen und habe auf meine Todo-Liste “REST-API für CinePoll” geschrieben. Wie bei fast allen Technologien kann man ja viel drüber lesen, aber verstehen wird man es erst wenn man es nutzt. Interessant fand ich die Anmerkung, dass ja “im Prinzip” keiner mehr den großen Server am Start hat, sondern stattdessen alle Microservices im Docker-Container deployen. Dazu kann ich nur sagen: in manchen Branchen ist man noch lange nicht soweit.

Im nächsten Zeitslot habe ich ReactiveX mit RxJava besucht. Ursprünglich wollte ich da nicht hin, weil der vorgesehene Vortragende bei mir bei einer vorherigen Ausgabe des Java Forum einen eher durchwachsenen Eindruck hinterließ, und ich die Erfahrung gemacht habe, dass ein guter Referent wichtiger ist als ein spannendes Thema. Jan Blankenhorn hat hier seine Sache jedenfalls sehr gut gemacht, inklusive Live-Coding, was ja immer einen gewissen Risikofaktor beinhaltet. Jedenfalls nahm ich mindestens zwei Erkenntnisse mit: ich muss doch IntelliJ IDEA eine zweite Chance geben, und ich muss dringend Routine bei den neuen Java 8-Features bekommen – bei der Lambda-Notation muss ich immer zweimal hinschauen, um den Code zu verstehen. Da ich täglich im Beruf auf Java 7 limitiert bin, fehlt einfach der routinierte Blick darauf.

Dann war es wieder Zeit für Michael Wiedeking, diesmal mit dem Thema Über den Umgang mit Streams. Wie immer sowohl unterhaltsam als auch lehrreich. Und wieder ein Knoten im Taschentuch für “Java-8-Features”. Und sehr plastische Beispiele für die Komplexitätsbetrachtung von Operationen.

Auch den Slot vor dem Mittagessen bestritt ein alter Bekannter: Björn Müller von der CaptainCasa GmbH mit dem Thema Von Java-Swing, über JavaFX zu HTML für operativ genutzte Geschäftsanwendungen. Er berichtete von der Reise des CaptainCasa-Frameworks von der Swing-UI über die JavaFX-UI hin zur neuen HTML-UI. Der Ansatz von “RISC-HTML” führt zu höchst erstaunlichen Ergebnissen, die damit erreichte Performance der Oberfläche im Browser war aus meiner Sicht verblüffend. Die nicht immer ganz hasenreinen Ausführungen und Analogien zu CISC-vs-RISC bei den Prozessoren habe ich da gerne verziehen. Interessant auch die Einschätzungen und Erfahrungen zu JavaFX, die sich durchaus mit meinen Beobachtungen decken: am Markt gescheitert, Zukunft ungewiss. Die Krise der Cross-Platform-UI-Toolkits geht weiter.

Nach der Mittagspause ging es bei mir weiter mit Mobile hybride Applikationen – Investment-App der BW-Bank, präsentiert von Manfred Heiland von der avono AG. Cross-Platform-Lösungen interessieren mich seit Anbeginn der Java-Zeit mit dem Versprechen “Write Once, Run Anywhere”. Was auf der Serverseite nach wie vor prima funktioniert, ist im Bereich der grafischen Oberfläche ja schon längere Zeit eher schwierig. Swing war der letzte aus meiner Sicht einigermaßen erfolgreiche Versuch, ein Cross-Platform-UI-Toolkit an den Start zu bringen. Aber bezüglich mobiler Plattformen bringt das halt auch nix. Dort hat man im Prinzip die Wahl auf WebViews zu setzen, oder halt mehrfach native Apps zu bauen – ob Technologien wie Gluon hier irgendwann adäquate Lösungen erlauben, die auch über viele Jahre stabil bleiben, ist aus meiner Sicht noch unklar. Die Tatsache, dass iOS Objective C oder Swift nahelegt und Android Java erleichtert die Sache nicht. Und hier bietet das vorgestellte Projekt mit seinem Hybridansatz eine interessante Alternative. Die fachlogisch einfachen Teile, die die Steuerung der App übernehmen, werden nativ implementiert, diverse komplexere Inhalte hingegen, die hauptsächlich der Anzeige dienen, werden über WebViews gemacht. Offenbar gibt es inzwischen sowohl unter Android als auch unter iOS einigermaßen elegante Lösungen, per JavaScript in beide Richtungen zu kommunizieren, so dass mir der hybride Ansatz sehr plausibel erscheint. Laut Referent gab es auch keine Probleme, die Apps in die App-Stores zu bringen – das ist bei “ich kapsle meine Webanwendung einfach als App”-Variante ja nicht immer so problemlos.

Als passionierter Frontend-Entwickler habe ich dann natürlich UI Technologieradar besucht. Das erwies sich als nicht so besonders fruchtbar. Die versprochene “objektive Entscheidungshilfe bei der UI-Technologiefindung” erwies sich als eher simple Ansatz einer Bewertungsmatrix verschiedenster (aber immer noch sehr lückenhafter) UI-Technologien, wobei die Bewertungen sich als durch und durch subjektiv erwiesen. Die ausgearbeiteten Kriterien entsprachen ungefähr dem, was ein etwas erfahrener Consultant oder Entwickler nach 30 Minuten scharfem Nachdenken selbst erarbeitet hätte. Und es traten verschiedene Einschätzungen und Aussagen zu diversen Technologien hervor, die die Glaubwürdigkeit der ganzen Idee doch in einem ziemlich schalen Dämmerlicht erscheinen ließen – beispielsweise wurde die These aufgestellt, dass mit Java Swing ja nie jemand eigene Komponenten erstellt habe, während das bei diversen Webtechnologien gängige Praxis sei. Aus meiner Sicht eine komplett absurde Einschätzung und nur durch sehr enge Scheuklappen aus der eigenen Projekterfahrung zu erklären – was der Consultant noch nicht gesehen hat, gibt es nicht. Zu allem Überfluss bildeten die beiden Vortragenden auch noch einen scharfen Kontrast bezüglich ihrer präsentatorischen Fähigkeiten, was sich als sehr störend herausstellte. Präsentieren im Duo geht aus meiner Sicht nur, wenn beide sich entweder gut ergänzen oder gut aufeinander eingespielt sind. Keines von beidem war hier der Fall. Und als Schlusspunkt: das “UI Technologieradar” wurde in der Vortragsankündigung als fertig einsetzbares Tool charakterisiert – im Vortrag stellte sich allerdings heraus, dass es sich noch in einem sehr frühen Stadium der Entwicklung befindet. Ebenfalls in der Ankündigung: man würde Technologien wie React.js oder Spring MVC kurz kennenlernen. Das entpuppte sich als stark übertrieben: Spring MVC beispielsweise tauchte nur namentlich auf einigen Folien auf. Interessant auch ein innerer Widerspruch: auf mehreren Folien tauchte die These auf, dass GWT stark auf dem absteigenden Ast sei. Empfehlung: nicht mehr für neue Projekte verwenden. Kann man so sehen. Aber die Begründung, warum AngularJS so eine gute Wahl sei, war, dass eine große Firma wie Google dahinter steht. Finde den Fehler.

Den Abschluss bildete für mich die Pecha-Kucha-Session. Auch dieses Mal muss ich feststellen: das Format gibt mir überhaupt nix. Wenn der Vortragende gut ist (wie in diesem Falle mal wieder Michael Wiedeking), merkt man im Vortrag gar nicht, dass es im Pecha-Kucha-Style abläuft. Wenn der Vortragende schlecht ist (bzw. den Ablauf nicht häufig genug geübt hat, um das passende Timing zu entwickeln), passt die Folie nicht zum Gesprochenen. Und das Format scheint zur Nutzung nichtssagender Bilder zu animieren, denn dann passt die Folie auch automatisch zum gesprochenen Wort. Auf die einzelnen Vorträge (klassisches 20×20-Format, 5 Vorträge für die zur Verfügung stehenden 45 Minuten) will ich gar nicht detailliert eingehen und will nur den von Michael Wiedeking lobend hervorheben, der unter dem Titel “Schall und Rauch” sehr plastisch ausführte, wie kompliziert es ist sprechende Methodennamen zu finden. Aus meiner Sicht übrigens der Knackpunkt der CleanCode-Bewegung und ihrem Mantra “Kommentare überflüssig, der Code erklärt sich von selbst”. Das zeigte sich, als das Publikum abstimmen durfte, welcher Methodenname am passendsten zur beschriebenen Operation sei. Da herrschte kein klares Meinungsbild, was die Problematik doch sehr verdeutlicht. Essenz daraus: entscheide Dich für ein Wording, aber ziehe das dann auch konsistent durch – Du wirst es nie allen recht machen können.

Wer Beispiele für gelungene Pecha-Kucha-Vorträge kennt (also nicht nur: guter Vortrag, sondern Mehrwert durch die Tatsache, dass er dem Format folgt): gerne Links an mich schicken, vielleicht ändere ich meine Meinung.

Unterm Strich fand ich die Veranstaltung sehr gelungen. Die Vielfalt der Vorträge gefällt mir gut, preislich ein Schnäppchen, Top-Organisation, Verpflegung OK. Gut, die Liederhalle ist etwas in die Jahre gekommen, aber das stört nicht sonderlich. Also: nächstes Jahr wieder dabei. Hoffentlich wieder bei Vorträgen von Michael Wiedeking und Björn Müller.

Swing-Rätsel – JTree-Zeilenhöhe

 Java, Swing  Kommentare deaktiviert für Swing-Rätsel – JTree-Zeilenhöhe
Apr 112016
 

Manchmal hat man Probleme schon vor so langer Zeit gelöst, dass man sich bei erneutem Auftreten nicht mehr dran erinnert. Weder an das Problem, noch an die Lösung.

Es geht um Java Swing. Die Basics. Man nehme einen JTree. Da gibt es eine Methode namens “setRowHeight” wo man die zu rendernde Zeilenhöhe setzen kann. Wie so oft gibt es hier auch eine “magic number”, die besonderes Verhalten aktiviert. Hier ist es die 0 (oder negative Werte) – damit instruiert man den Tree, doch bitte den Renderer zu fragen, wie hoch die Zeile denn nun wirklich werden soll. Man sollte meinen, dass das auch ein wirklich guter Default wäre.

Nun gibt es ja in Swing zwei Varianten, wie so ein Default in Aktion tritt. Entweder simple Hartcodierung, oder durch die UI-Defaults des aktiven Look&Feel. Wer weiß auswendig, wie der hartcodierte Wert ist und welches der Standard-Look&Feels dann welchen Wert setzt? Bonuspunkte für denjenigen, der das auch noch schlüssig erklären kann.

Amüsante Randnotiz (Achtung, Spoiler! Teile der Antwort auf obige Frage inside!): hier ein Bug aus Zeiten von Java 1.4 (gab es das wirklich schon 2002?), der sich ebenfalls mit diesem Problem befasst.