hubersn

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.

Beknackte Oberflächen – heute: Windows 10 Timeserver konfigurieren

 UI  Kommentare deaktiviert für Beknackte Oberflächen – heute: Windows 10 Timeserver konfigurieren
Mai 162019
 

Mein Klagelied über mein “Übergangsauto” lässt noch auf sich warten, deshalb heute zur Erholung ein weiteres Kleinod aus der Reihe “Beknackte Oberflächen”.

Die Vorgeschichte; ein Phänomen zeigte sich auf einem einzigen Windows 10-Rechner in meinem Heimnetz: die Uhrzeit war regelmäßig falsch. Also nicht nur ein paar Minuten, sondern Stunden oder wie heute ein ganzer Tag. Mal vor, mal nach. Besonders bei E-Mails, die man schreibt, kann das zu Irritationen führen.

Man konnte das Problem für den Moment stets beheben, wenn man in den Uhrzeit-Einstellungen die automatische Synchronisation mit einem Internet-Timeserver kurz abschaltet und wieder anschaltet. Das bewährte IT-Rezept – “have you tried to turn it off and on again” – in voller Blüte.

In den Einstellungen gab es hingegen keinen offensichtlichen Weg, einen spezifischen Timeserver einzustellen, denn offenbar war der Default ja ein sehr unzuverlässiges Exemplar seiner Gattung. Eine kurze Internet-Recherche später stellt sich raus: der Weg zur Einstellung des Timeservers ist nicht nur nicht offensichtlich, er ist extrem unoffensichtlich. Man muss den Link “Uhren für unterschiedliche Zeitzonen hinzufügen” klicken, und schon öffnet sich ein guter alter Windows-XP-Style-Dialog, der auch einen Reiter “Internetzeit” hat. Dort gibt es dann einen Button, der einen weiteren Dialog öffnet (klar, dass man für diese kritische Aktion Admin-Rechte braucht). Flugs die FritzBox eingetragen, scheint zu funktionieren. Mal sehen wie lange. Besser als mit dem alten Server (natürlich einer unter microsoft.com – sehr beruhigend, das Betriebssystem eines Herstellers einzusetzen, der nicht mal einen zuverlässigen Timeserver betreiben kann) wird es auf jeden Fall sein.

Gastbeitrag: Ein Nachruf für meinen Audi A6 Avant

 Rant, UI  Kommentare deaktiviert für Gastbeitrag: Ein Nachruf für meinen Audi A6 Avant
Nov 042018
 

Vorwort von hubersn: Jeder große Blog veröffentlicht regelmäßig Gastbeiträge. Vermutlich ist die Kausalität dieser Korrelation so, dass der Blog erst mal groß wird und dadurch zu Gastbeiträgen kommt. Ich versuche es jetzt mal andersrum. Denn das Schicksal wollte es so, dass mein lieber Freund und Kollege Roland ein ähnliches Schicksal erfuhr wie ich – ein hoffentlich nur temporärer, kurzer Wechsel von einem “Wunschfahrzeug” zu einem “war-gerade-übrig-Fahrzeug”. Da hier im IT-Blog regelmäßig über schlechte Usability und bescheuerte Oberflächen geschimpft wird, erschien mir sein Beitrag sehr passend – mein Beitrag zu diesem Thema folgt demnächst. Aber genug der Vorrede, lassen wir Roland zu Wort kommen…

Es war ein trauriger Tag. Ich verabschiedete mich nach drei Jahren von meinem geliebten, geleasten Firmenwagen. Der Nachfolger sollte ein A5 Cabrio werden… aber mein Chef hatte eine andere Idee.

Und so erlebte ich völlig neue Erfahrungen. Ich hätte nicht gedacht, dass Audi noch so viel Vorsprung durch Technik hat. Aber eins nach dem anderen. Ein Jetzt-Ex-Kollege hat einen wenige Monate neuen Volvo V90 hinterlassen und mein Chef sagte, jetzt fährst Du den mal. Ich sagte maximal ein Jahr – ich hatte da so eine Vorahnung. China und Schweden. Geht das gut?

Guten Mutes fuhr ich also mit dem V90 los. Ich fahre jede Woche gut 400 Kilometer Autobahn und da ruft mich gerne eine Kollegin an. Auf einer der ersten Fahrten war das mal wieder der Fall. Auf dem BlackBerry unserer Firma hat sie mich nicht erreicht. Warum? Ich habe es bisher nicht geschafft, dem Volvo beizubringen, dass ich zwei aktive Mobilverbindungen benötige. Vielleicht lese ich doch irgendwann die im Auto integrierte Anleitung, die man nur im Stand lesen kann. Es gibt ja keine Beifahrer, die während der Fahrt lesen können – so etwas ist in Europa nicht denkbar. Zum Glück kennt meine Kollegin meine private Nummer. Ich gehe ans Telefon, wir sprechen kurz. Dann fragt sie mich leicht verwirrt, was ich denn gerade mache. Sie dachte, ich wäre im Auto unterwegs. Ich sage, dass ich im Auto unterwegs bin. Sie meint, es gäbe so laute Nebengeräusche und sie versteht mich so schlecht. Ich sage nur: “Ich fahre jetzt Volvo”.

Die schönste Neuerung im Volvo ist das große, glänzende Touch-Display über der Mittelkonsole. Da freut man sich. Super hell und nur im Nachtmodus dimmbar. Ja, tatsächlich funktioniert der Drehknopf links neben dem Lenkrad nur im Nachtmodus – wirkt sich aber selbstverständlich auf den Tagmodus aus. Man muss also einen Tunnel finden, um tagsüber die Einstellung zu ändern. Schon mal das Wort Benutzerfreundlichkeit in Schweden gehört? Nix “lindra” im Volvo.

Das ist aber nicht die größte Schwäche des tollen Displays. Das ist der Touch selbst. Man wird wahnsinnig in Kombination mit Android Auto. Ob es an Google oder Volvo liegt ist mir egal. Im Stand kann man Audible, Amazon Music und Google Maps wenn man sich am Rand des Displays festhält ja noch halbwegs bedienen. Aber während der Fahrt wird das zum Geduldsspiel. Man tippt lässig, um eine Aktion auszuführen. Das Display scrollt hoch und runter ganz munter aber Aktionen ausführen – seltenst. Da muss man vorausschauend fahren und an roten Ampeln die Playlist auswählen – mit ruhiger Hand. Wer keine ruhige Hand hat – Finger weg vom Volvo!

Es gibt ein zweites, mattes Display hinter dem Lenkrad. Vom Audi gewohnt, suche ich heute nach 6 Monaten immer noch nach dem Schalter, welcher die Karte da vorne verdrängt und mir sinnvolle Dinge anzeigt. Fehlanzeige. Oder zu gut versteckt im Handbuch? Tipps nehme ich gerne entgegen.

Von A nach B fährt inzwischen jedes Auto. Wenigstens meistens. Mein Audi konnte bei 220 km/h noch die Kurve alleine fahren. Beim Volvo ist bei knapp 140 km/h Schluss. Ist ja klar. Die Schweden haben sich für die Höchstgeschwindigkeit 120km/h entschieden. Da darf man alles darüber nicht testen und schaltet einfach ab. Und dann ist der Volvo ein Nervenbündel beim Spurhalten. Zuckelt links und rechts anstatt ruhig und elegant wie der Audi zu fahren. Was für ein Rückschritt zu einem mindestens drei Jahre älteren Audi. Vorsprung durch Technik kann ich da bestätigen.

Man erträgt es halt und wendet sich den guten Dingen im Volvo zu. Die Sitze sind wirklich gut. Leder. Und der Volvo speichert alle möglichen und unmöglichen Einstellungen in einem Fahrerprofil im Schlüssel. Leider auch wenig durchdacht. Ich würde lachen, wenn es nicht so traurig wäre. Umluft kann man auch ganz einfach mit wenigen Berührungen der sehr großen Schaltflächen aktivieren. Umständlicher als jeder Knopf wie man es von früher kennt. Vor Touch im Auto. Gute Zeiten waren das, sage ich euch. Mit viel Tippen und Wischen findet man viele Einstellungen. Während der Fahrt nur ratsam, wenn man den Zappelphilipp von Pilot-Assistent aktiviert. Und immer schön hin- und herschauen, ob im matten Display das Lenkrad grün ist, sonst zappelt nichts mehr von alleine am Lenkrad. Dann ist der Assistent plötzlich aus.

Lustig – eher traurig – ist auch der Assistent, der verhindern soll, dass man beim Rückwärts- oder langsam Vorwärtsfahren nichts beschädigt. Das Ding geht aber manchmal plötzlich aus und man merkt es nicht. Damit ist es irgendwie sinnlos geworden, oder?

Zur Verarbeitung: Ich habe Grünschnitt transportiert und die klapprige Kofferraumabdeckung entfernt. Danach schnell wieder einbauen. Denkste. Die rastet irgendwie nicht ein. Ich wollte schon den Hammer holen. Beim Audi flutscht das nur so. Klack und fertig. Okay. Beim Audi wiegt das Ding das Zehnfache. Wirkt solide und funktioniert. Und dann frisst die Volvo Abdeckung auch noch eine Ecke meiner besten Wellensteyn-Jacke! Die Ablage oben sollte man niemals für Jacken benutzen! Das steht bestimmt im Handbuch. Habe ich ja nicht gelesen. Bin da halt noch Papier gewöhnt. Beim Öffnen frisst der Einzug die Jacke an und gibt sie nicht mehr her! Schließlich musste ich die Jacke herausreißen und damit eine Ecke am Kragen abreißen.

Neulich dann der Höhepunkt. Mein Chef fährt mit und ist ganz begeistert, wie toll das Auto ist. Nun muss man wissen, dass mein Chef das älteste Technik-Kind der Welt ist, Porsche fährt und jeden Technikschnickschnack liebt. Er kennt sich aus und steigt ein und meint, dass man da aber toll sitzt und das wäre ja ein super Display. Wow! Gestochen scharf. Ich meine nur: Aber nicht bedienbar – touch.

Das probiert der Chef begeistert aus und will mit Google Maps das Ziel eingeben. Er versucht den Knopf für das Mikrofon auszulösen. Wir fahren bereits. Beim 5. Versuch klappt es – wir stehen an der Ampel. Google findet das Ziel und Chefe will es auswählen zum Starten der Navigation. Im Heslacher Tunnel beweise ich ihm, dass man das Display tatsächlich abblenden kann – nur im Tunnel. Schon nach dem Tunnel schaffen wir es die Navigation zu starten. Jetzt bräuchten wir wieder einen Tunnel, damit wir das Display wieder heller machen können. Google lenkt uns nach rechts vorbei am nächsten Tunnel. Blöd gelaufen.

Ich denke, beim nächsten Gespräch mit meinem Chef gesteht er mir im Frühjahr ein Cabrio zu. Ich bin ja als Bereichsleiter durchaus ein Mitarbeiter, den man halten will, oder?

Roland

MISTer-Fortschritte und die zweite Luft für MIST

 Hardware, Retrogaming  Kommentare deaktiviert für MISTer-Fortschritte und die zweite Luft für MIST
Okt 302018
 

Schon lange nichts mehr zu MISTer und MIST geschrieben (zuletzt im Juni 2017). Dabei gibt es großartige Fortschritte bei beiden Projekten.

Der MISTer hat einen neu implementierten CPC-Core bekommen, der um Welten besser funktioniert als die alte, ursprüngliche Variante. MISTer-Mastermind Sorgelig hat viel Zeit darauf verwendet, besonders die CRTC-Implementierung (ein Motorola 6845, der in verschiedenen Varianten im CPC verbaut wurde, die sich alle in Kleinigkeiten unterscheiden, was von verschiedenen Demos auch weidlich ausgenutzt wird) und auch teilweise die Z80-Implementierung sowie die Floppy-Simulation (NEC 765), um endlich auch diverse Kopierschutzmaßnahmen sowie Sonderformate zu unterstützen.

MIST und MISTer profitieren gerade gemeinsam von einer Weiterentwicklung des Sega Genesis-Cores (für Europäer: Sega Megadrive), bei der unter anderem Sorgelig, GreyRogue und MIST-Mastermind Till Harbaum (“MasterOfGizmo” im Atari-Forum) zusammenarbeiten, nebst einem Spezialisten (“Jotego” im Atari-Forum) für die Soundchips des Genesis und mehreren Kennern der Originalspiele, die in langwierigen Testläufen die diversen Änderungen, die teilweise im Stundentakt gemacht werden, auf Regressions prüfen.

Im Zuge dieser Anpassungen und Weiterentwicklungen wurde auch für den MISTer eine neue Art des Scalings für den HDMI-Output implementiert auf Basis des “Nearest Neighbor”-Algorithmus, der diverse Artefakte des bisher benutzten Scalers verhindert (und dafür auf gewissen Hardware-Kombinationen aber auch neue Artefakte erzeugt). Das ist noch “work in progress” und derzeit eine Compile-Time-Einstellung, es sieht aber so aus wie wenn über zur Laufzeit ladbare Koeffizienten beide Skalieralgorithmen gleichzeitig im selben Core leben können. Und ein komplett neuer Scaler ist derzeit in Entwicklung – Stand heute ist die Scaler-Geschichte etwas unschön, weil sie spezielle IP erfordert, und diese nur für eher teure Quartus-Versionen (quasi die “Entwicklungsumgebung” für die Intel/Altera-FPGAs) verfügbar ist und die freien Versionen solche Cores nicht bauen können. Mit einem unabhängigen Open-Source-Scaler wäre dieses Hindernis auch aus dem Weg geräumt.

Lange Zeit fehlte ein Atari ST-Core beim MISTer – eigentlich die einzige große Lücke, alle anderen Cores waren längst auf dem MISTer portiert und teils sogar stark verbessert gegenüber ihrem MIST-Original. Und hier gibt es den vermutlich größten Fortschritt: ein komplett neuer Core namens FX CAST (von Jorge Cwik aka ijor im Atari-Forum), basierend auf einer zyklenexakten Nachbildung des Motorola 68000 und einer sehr präzisen Nachbildung von Grafik- und Soundchip. Die Sourcen dazu sind noch nicht offen, das soll aber demnächst so weit sein.

Also, ran an den MISTer, oder den alten MIST nochmal auspacken. Es gibt für den MIST inzwischen viele Cores, die das “Component out”-Kabel unterstützen, was es deutlich erleichtert, den MIST an einigermaßen aktuelle Fernseher oder Projektoren anzuschließen – bei vielen Bildgeräten hat “Component” neben HDMI als Input-Schnittstelle überlebt, während Scart-RGB mit der Lupe gesucht werden muss, genauso wie VGA-Eingänge mit ausreichender Flexibilität für die “krummen” Videosignale der diversen Cores.

Für den MISTer gibt es auch eine neue Version des USB-Hub-Boards mit der Wiederauferstehung des 9pol-Digital-Joystick-Anschlusses. Und es wird gerade mit Serial-to-MIDI experimentiert, um auch noch diese letzte MIST-MISTer-Lücke zu schließen.

Rundrum großer Fortschritt und viel Bewegung und Weiterentwicklung. Es ist eine Freude, das zu verfolgen, und ein schönes Beispiel für “Open Source funktioniert”. Besonders freue ich mich, dass Till wieder aktiv ins Geschehen eingreift, er klang zuletzt etwas negativ bezüglich der MIST-Zukunft (Produktion eingestellt, im Prinzip Abverkauf der letzten Exemplare, und Ärger mit Billig-Nachbauten wie Mistica die ihre Kunden im Regen stehen lassen). Er scheint wieder neue Energie gefunden zu haben.

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.

Die zunehmende Entmündigung des Nutzers

 Rant  Kommentare deaktiviert für Die zunehmende Entmündigung des Nutzers
Apr 182018
 

Gerade hat sich mein Kindle eigenmächtig upgedated. Als ich ihn ein paar Minuten unbeaufsichtigt zwecks Lesepause, aber noch verbunden mit dem WLAN, liegen gelassen habe. Und es hat Minuten gedauert. Und er hat den alten Lesezustand leider vergessen. Ich hatte vor ein paar Tagen das Update noch explizit abgelehnt aufgrund von “never change a running system”.

Das ist eine zunehmende Unsitte von IT aller Art, gar nicht mehr auf die Zustimmung des Nutzers zu warten, ihn gar nicht mehr zu informieren was denn das Update so macht. Ihm die Entscheidung zu überlassen. Wäre auch nicht das erste Mal, dass Features von einer Version zur nächsten einfach Verschwinden. Zurück zu einer älteren Version kann man ja auch nur noch in den seltensten Fällen. Der Traum eines modularen Upgrademechanismuses, wo man “pick and mix” mit gewünschten und unerwünschten Updates machen kann, ist ja schon seit Jahrzehnten ausgeträumt. Langfristige Stabilität und Support etwas von gestern – Vorwärtsstrategie scheint das neue Zauberwort.

Irgendjemand wird jetzt sicher “Security” sagen. Ja, dann macht es doch einfach sicher. Ein stabiler Branch mit aktuellen Sicherheitspatches. Einen Development-Branch für die Mutigen. Das wäre das allermindeste, denn bei der üblichen Qualität heutiger Softwareentwicklung bedeutet ja jede Änderung ein neues Sicherheitsrisiko. Und bei einem komplett vernagelten System wie dem Kindle kann Security wohl kaum ein Argument sein, oder jemand hat wirklich Scheiße gebaut.

Ich hasse Intransparenz. Besonders, wenn es um meine eigenen Geräte geht. Macht im Web was ihr wollt. Aber nicht auf meinen Geräten.

Die zunehmende Nutzlosigkeit von Datenblättern

 Uncategorized  Kommentare deaktiviert für Die zunehmende Nutzlosigkeit von Datenblättern
Apr 082018
 

Wann immer ich ein Stück Technik kaufe, informiere ich mich vorher so gut es geht. Reviews bei Amazon, Testberichte, und natürlich die Hersteller-Website. Bei komplexeren Dingen hilft oft ein Blick ins Handbuch, das ja Gott sei Dank inzwischen fast immer zum Download verfügbar ist (und das auch sein muss, denn gedruckt beigelegt wird es heutzutage ja immer seltener – aber das ist eine andere Geschichte).

Seit Jahren beobachte ich dabei eine ständige Reduktion der Tiefe technischer Daten. Jahrelang habe ich z.B. bei DVD-Brennern den Strombedarf zu ermitteln versucht – meist stand nur “5V/12V” im “Datenblatt”. Sehr nützlich.

Ein besonders sparsames Beispiel ist mir gerade untergekommen: es geht um eine externe USB-Festplatte von Seagate. Was könnte es da an nützlichen Informationen geben, die man in ein Datenblatt schreiben könnte? 512 Bytes/Sektor vs. 4Kn? Strombedarf am USB, womöglich gar getrennt nach Anlaufstrom, Strombedarf im Betrieb und im Standby? rpm der verbauten Platte? Geräuschentwicklung? Ob es intern eine S-ATA-Platte ist und die USB-Schnittstelle mit SAT arbeitet? Format bei Auslieferung?

Man werfe einen Blick drauf und ergötze sich an zwei Druckseiten Nichtinformation.

Immerhin: wir wissen nun, dass in 320 bzw. 240 Hauptkartons pro Palette geliefert wird. Super!

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;
  }
}