Dienstag, 4. März 2014

Java Web Start Anwendungen remote debuggen

Heute habe ich eine JNLP bzw. Java Web Start-Anwendung debugged. Naja, ich wollte gerne. Ging aber nicht. Manchmal sind die Sachen, die eigentlich trivial sein sollten die schlimmsten. Die übliche Vorgehensweise ist wie folgt (in cmd.exe):

set JAVAWS_VM_ARGS="-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000“ (unter WinXP/Windows 2008 Server ohne Anführungszeichen)
javaws http://server_name_or_ip/jnlpfile.jnlp  (JNLP-Datei der Anwendung aufrufen)

Das funktioniert auf den ersten Blick auch ganz gut. Die Anwendung wird geladen, gestartet und man kann sich mittels Eclipse damit verbinden und sieht die Threads der Anwendung. Jetzt kommt das Problem: Man möchte fröhlich drauf los debuggen, plötzlich wird die Verbindung beendet. Was ist passiert? Man hat den JNLP-Launcher debugged. Er beendet sich und startet die eigentliche Anwendung. Die weiss leider nichts davon, dass man sie gerne debuggen möchte.

In diesem Fall wird mit Java 6 u 45 gearbeitet. Ähnliche Probleme scheint es aber auch mit Java 7 zu geben.

Was tun? Alle Tipps, die ich finden konnte funktionierten nicht: z.B. die -Xnofork Option. Oder die Argumente mittels javaws -J-Xdebug -J-Xnoagent -J-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 zu übergeben.

Irgendwo hatte ich gelesen, dass bestimmte Java-Optionen (z.B. Debug-Optionen!) wenn sie im JNLP-File stehen von JWS ausgefiltert werden. Sollte dies tatsächlich das Problem sein? 

Ein Blick in die JNLP-Datei offenbarte die folgende Zeile:
<j2se version="1.6.0_07+" java-vm-args="-Xss512K" initial-heap-size="128m" max-heap-size="512m" href="http://java.sun.com/products/autodl/j2se"/>

Nachdem ich sie wie folgt abgeändert hatte funktioniert auch das debuggen: <j2se version="1.6.0_07+" href="http://java.sun.com/products/autodl/j2se"/>

Fazit: Scheinbar werden die Java-Optionen von JAVAWS_VM_ARGS irgendwo mit denen aus der JNLP-Datei gemerged und dann ausgefiltert! Natürlich nur dann, wenn in der JNLP-Datei Java-Optionen stehen. Entfernt man also alle Optionen, die zu "-X" Optionen übersetzt werden können aus der JNLP-Datei läuft es erwartungsgemäß. Grummel.



Mittwoch, 25. Mai 2011

Objekte die viel zu viele Listener-Interfaces implementieren - kann Eclipse da weiterhelfen?

Heute möchte ich einmal meine Begeisterung für eine Eclipse-Funktion teilen, die ich letztens entdeckt habe.

Was ich persönlich ja gar nicht mag, sind Klassen, die einige Listener-Interfaces zugleich implementieren. Als Interims-Entwickler und Source-Doktor sieht man so was irgendwie öfters.  Es ist dann meistens sehr schwer, diejenigen Methoden zu finden, die die eigentliche Logik des Objektes darstellen.
In dem - trotz der leeren Methoden - unübersichtlichen uns sehr konstruiertem Beispiel wären das die Methoden  macheWasUnuebersichtliches() und tueWasAnderesUnuebersichtliches()
Nunja, letztlich sind Listener bloß Objekte die dazu dienen, andere Objekte miteinander zu „verkitten“. Sie sollten deshalb nicht mit lauter public-Methoden die eigentlich so schöne Schnittstelle der Klasse „verschmutzen“.
Ich gehöre zur Fraktion derjenigen, die Listener-Interfaces lieber innerhalb der Klasse als anonyme Klasse implementieren. 
Aber es geht mir nicht darum, über Best Practices zu grübeln.
Was also tun, wenn der Code schon da ist und bereits 10 Jahre auf dem Buckel hat.  Man ist gerade knapp dran, weil im Fernsehen gleich die "Supernanny" läuft und man möchte es deswegen lieber nicht sofort umbauen :-) Also erstmal verstehen, wie alles zusammenhängt? Das ist immer eine gute Idee - gerade bevor man es umbaut. 

Noch schwieriger wird es, wenn die Klasse viele 100 Zeilen Code enthält. Dann kann man kaum noch was erkennen und schwer auseinanderhalten welche Methode zu welchem Interface gehört.


Das macht es sehr schwer später ein Refactoring zu durchzuführen... hehe... :-)




In solchen Situationen wäre es schön, wenn man eine Übersicht hätte, in der die Methoden entsprechend der Interfaces sortiert sind. So ähnlich wie die Outline in Eclipse.
Das geht! Wenn auch nicht mit der Outline. Mit einem Rechtsklick auf den Klassennamen öffnet sich das gezeigte Kontextmenü:





Das Ergebnis ist schon recht nah am gewünschten Ziel:






Simsalabim, man sieht sofort, welche Methode zu welchem Interface gehört. Via Doppelklick auf den Namen gelangt man bequem an die richtige Stelle...  



Praktisch, oder? ... Genau!

Dienstag, 5. April 2011

Ist bei einem "instanceof" vorher eine null-Abfrage nötig?

Letzten Freitag habe ich mal wieder Quelltext-Reviewing in einem fremden Projekt betrieben und bin auf so etwas Ähnliches gestoßen: 


if (inputValue != null && inputValue instanceof String)
{
[...]
}


inputValue ist ein Object.


Ich gebe zu, es ist nur eine Kleinigkeit. Kaum der Rede wert. Aber weil ich so etwas schon oft gesehen habe: Ist eine Überprüfung auf null vor einem instanceof wirklich notwendig?






Nein,  der instanceof-Operator liefert nur dann true, wenn inputValue eine Instanz vom Typ String ist. Wenn die Variable inputValue also null enthält, kann das Objekt auf das inputValue verweisen würde wenn es nicht null wäre also unmöglich ein String sein... 


Hm, der letzte Satz ist toll.... :-)

Freitag, 25. März 2011

Böse Überraschungen bei lange laufenden Programmen, die mittels Runtime.exec() gestartet wurden

Heute möchte ich auf einen Fehler eingehen, den ich in den letzten Jahren bereits in verschiedenen Projekten entdeckt habe.
Manchmal ist es notwendig - aus einem Java-Programm heraus - externe Programme zu starten. Dafür gibt es die Methode Runtime.exec(). Die ruft man auf und übergibt das zu startende Programm. 
Soweit so gut. Allerdings gibt es da ein kleines Detail, welches oft übersehen wird. Selbst Programme die erstmal scheinbar erwartungsgemäß laufen, können deshalb nach einiger Zeit unerklärlich „blockieren“ oder gar abstürzen.
Was passiert da? 
Die meisten Programme schreiben Ausgaben oder Debug-Meldungen nach stdout bzw. stderr. Diese Ausgaben landen dann je nach Plattform in irgendwelchen Puffern des Betriebssystems. Was nun passiert ist vom jeweiligen Betriebsystem abhängig. Bei manchen Systemen kann der Puffer ins „Unendliche“ wachsen und wachsen. In diesem Fall könnte das System irgendwann ausbremst werden, da womöglich Speicher ausgelagert werden muss. Bei anderen Betriebsystemen ist der Puffer irgendwann voll und das externe Programm wird blockiert oder gar vom Betriebsystem beendet - oder verhält sich sonst wie merkwürdig.
Das Java-Programm sollte also regelmäßig den Inhalt von stdout und stderr „abholen“ damit es da keine Probleme gibt. Die folgende Klasse übernimmt das. Anwendungsbeispiele:
StreamConsumer.consumeOutputs(Runtime.getRuntime().exec(....));
Falls man nach Beendigung des Prozesses an ggf. aufgetretene Meldungen interessiert ist:
Process process = Runtime.getRuntime().exec(....); 
StreamConsumer errors = new StreamConsumer(process.getErrorStream()); 
StreamConsumer output = new StreamConsumer(process.getInputStream()); 
process.waitFor();
System.out.println(errors.getCollectedOutput());
System.out.println(output.getCollectedOutput());
Und nun die Klasse:
public class StreamConsumer {
    private final StreamConsumerImpl impl;
    static public void consumeOutputs(final Process process) {
        new StreamConsumer(process.getInputStream());
        new StreamConsumer(process.getErrorStream());
    }
    public StreamConsumer(InputStream inputStream) {
        this(inputStream, false);
    }
    public StreamConsumer(final InputStream inputStream, final boolean keepOutput) {
        impl = new StreamConsumerImpl(inputStream, keepOutput);
        Thread thread = new Thread(impl);
        thread.setName("StreamConsumer Thread");
        thread.start();
    }
    public String getCollectedOutput() {
        return impl.getCollectedOutput();
    }
    private final class StreamConsumerImpl implements Runnable {
        private final BufferedReader bufferedReader;
        private final StringBuffer collectedOutput;
        private StreamConsumerImpl(InputStream inputStream, boolean keepOutput) {
            this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
            if (keepOutput == true) {
                collectedOutput = new StringBuffer();
            } else {
                collectedOutput = null;
            }
        }
        public void run() {
            try {
                String outputPiece;
                do {
                    outputPiece = bufferedReader.readLine();
                    if (collectedOutput != null && outputPiece != null) {
                        collectedOutput.append(outputPiece);
                    }
                } while (outputPiece != null);
            } catch (IOException ioe) {
                System.out.println("Konnte Ausgaben des Prozesses nicht abfragen");
                ioe.printStackTrace();
            } finally {
                if (bufferedReader != null) {
                    try {
                        bufferedReader.close();
                    } catch (IOException exception) {
                        System.out.println("Problem beim Schliessen des Ausgabe-Streams");
                        exception.printStackTrace();
                    }
                }
            }
        }
        private String getCollectedOutput() {
            return collectedOutput != null ? collectedOutput.toString() : null;
        }
    }
}

Freitag, 11. März 2011

Ein Highlight aus der Bahn welches ich niemanden vorenthalten möchte..

Letztens habe ich im Zug Platz genommen, die Jacke über die Beine gelegt, mein Schlafhörnchen aufgeblasen und die Nackenstütze von der Stuhllehne abmontiert. Der Zug fährt bereits, da kommt jemand mit einem Koffer herein. Ein sympathisch wirkender, älterer Herr mit grauem Haar und recht schick angezogen.
Er wuchtet den Koffer gerade in die Gepäckablage, da fährt der Zug über eine Weiche und macht sogleich einen spontanen Ruck nach rechts. Der Herr und sein Koffer fallen beinahe auf einen unschuldigen Fahrgast, der das Unheil so gerade noch durch beherztes Ausstrecken der Arme abwenden kann. „Ich hasse die Bahn“ brummt der neu Hinzugestiegene.
Dann verlässt er den Waggon und kommt Minuten später mit etwas zurück, was aussieht wie ein ca. 3 Meter langer, gerollter Teppich. Den haut er fast jemanden um die Ohren während er versucht das gute Stück unter etwa vier Sitzreihen zu verstauen.
In meiner Umgebung formiert sich bereits der erste Widerstand. Ich stelle mich schlafend und blinzle hin und wieder mal unauffällig.
Wieder verlässt der gute Mann den Waggon, um Sekunden später mit einer großen, recht schwer wirkenden Metallkiste zurückzukommen. Fragende Blicke breiten sich unter den Fahrgästen aus. 
Natürlich erfüllt sich unsere schlimmste Befürchtung - das er die Kiste auf die Gepäckablage heben möchte - nicht! Sichtbares Aufatmen unter den anderen Fahrgästen. So eine Kiste hat man lieber mitten im Gang, den Weg versperrend, aber sich nicht oben über dem Kopf.
Netterweise haben sich inzwischen drei Fahrgäste zusammen getan, um die Kiste des sichtlich überforderten Manns irgendwie an eine unauffällige Stelle, zwischen zwei Sitze zu bugsieren, die für sperriges Gepäck gedacht ist. 
Keine drei Minuten später kommt der Schaffner... „Die Fahrkarten bitte“. Natürlich waren alle Fahrgäste gespannt auf die sich anbahnende Diskussion wegen des Teppichs und schauen bereits schön unauffällig nach draußen - mit weit gespitzten Ohren!
Allerdings entwickelte sich das Gespräch etwas anders als erwartet.
„Bitte sehr.“ - „Vielen Dank, aber da ist kein Ticket dabei.“ - „Doch doch, die habe ich am Automaten gedruckt!“ - „Nein, nur eine Reservierung und eine Verbindungsübersicht.“ 
Am Ende des Gespräches war ich wirklich verwundert, dass der arme Mann nicht nochmals bekräftigt hat, wie sehr er das Bahnfahren hasst. Ich frage mich, ob er immer Dinge, die man besser mit dem LKW transportiert mit der Bahn herumgondelt. 

Freitag, 25. Februar 2011

Best practice: Fail-Fast, Exceptions und Assertions

Heute habe ich einiges an Fail-Fast-Code geschrieben, also eine günstige Gelegenheit mal darüber zu sprechen.

Eine einfache und preiswerte Möglichkeit die Qualität zu steigern und das Debuggen zu erleichtern ist der Fail-Fast-Ansatz.

Aber was bedeutet das? Es geht darum, einen Fehler auszulösen bevor er wirklich entsteht. Wir alle kennen die schöne NullPointerException. 

Meistens ärgere ich mich, wenn sie geworfen wird, denn leider hat man es oft mit Code zu tun, der nicht Fail-Fast implementiert wurde. Besonders unerfreulich ist das in fehlerhaften Code der schon beim Kunden ist. Ein einfaches Beispiel für Fail-Late:

class Example {
private int width = 5;
private int height = 8;
private int state = 0;

public void doCalculation() {
state = width * height + getAnotherIntegerValue();
delegate.getInsets().addOffsetX( state );
state += 2;
}
}

Und Peng! Eine NullPointerException in der 2. Zeile von doCalculation(). Aber was war jetzt null? "delegate" oder lieferte "Delegate.getInsets()" null? Das ist ein typisches Problem bei diesem Vorgehen, man bekommt nur wenige Infos darüber, was schiefgegangen ist.

Fail-Fast bedeutet, schneller zu sein und nicht zu warten bis vielleicht eine NPE fliegt, sondern an der frühst möglichen Stelle die Exception zu werfen:

public void doCalculation() {
if (delegate == null) {
throw new NullPointerException("delegate is null");
}
if (delegate.getInsets() == null) {
throw new IllegalStateException("delegate.getInsets() returned null");
}
state = width * height + getAnotherIntegerValue();
delegate.getInsets().addOffsetX( state );
state += 2;
}

Dieser Code hat verschiedene Vorteile. Man weiss sofort, was genau schief gegangen ist. Darüber hinaus können wir auch sicher sein, dass "state" nicht in einem seltsamen Zustand ist.

Klar, so hat man mehr if-Abfragen (wir möchten jetzt nicht über die Kosten dafür diskutieren). Es gäbe es auch noch folgenden Kompromiss (der auch lesbarer ist):

public void doCalculation() {
assert delegate != null : "delegate is null";
assert delegate.getInsets() != null : "delegate.getInsets() returned null";
state = width * height + getAnotherIntegerValue();
delegate.getInsets().addOffsetX( state );
state += 2;
}

Je nach Kompiler-Einstellung werden die assert-Befehle restlos entfernt. Oder halt nicht, was uns bei fehlerhaften Produktions-Code dann allerdings auch nicht weiterhilft. Aber das Test-Team hat ja gründlich getestet :-) Achtung: Assertations sind per Default abgeschaltet!

Das war natürlich ein sehr einfaches Beispiel. Wer sich mal anschauen möchte wie so etwas im "echten" Leben auch aussehen kann sollte sich mal die Klassen (z.B. ArrayList und den Iterator) im Collections-Package von JDK anschauen. Die erkennen Multitasking-Probleme mit Hilfe eines Modifikations-Zählers und werfen lieber früher eine ConcurrentModificationException statt irgendwann später merkwürdige Ergebnisse zu liefern.

Donnerstag, 24. Februar 2011

TIPP: Plattform- und Eingabegeräte-unabhängige Behandlung von Kontextmenüs

Möchte man sicherstellen, dass ein Popup-Menü bzw. Kontext-Menü auf jeder Plattform bzw. mit verschiedenen Eingabegeräten (z.B. Stift bzw. Grafiktablett statt Maus, oder Linkshändler-Maus) funktioniert, hier ein kleiner Tipp:

Statt in MouseListener.mouseReleased() über SwingUtilities.isRightMouseButton() abzufragen, ob die rechte Maustaste gedrückt wurde, kann man besser MouseEvent.isPopupTrigger() verwenden. Dies gibt plattform- und geräteunabhängig true zurück, wenn das Popup geöffnet werden soll.

Da auf einigen Plattformen das Popup bei einem mousePressed() und auf anderen bei mouseReleased() angezeigt wird und isPopupTrigger() nur im entsprechenden Fall true liefert, muss man daher beide MouseEvents behandeln.

Hier ein kleines Code-Beispiel:

      private class PopupHandler extends MouseAdapter
      {
            @Override
            public void mousePressed(MouseEvent e)
            {
                  handlePopup(e);
            }


            @Override
            public void mouseReleased(MouseEvent e)
            {
                  handlePopup(e);
            }


            private void handlePopup(MouseEvent e)
            {
                  if (e.isPopupTrigger())
                  {
                        showPopup(e.getX(), e.getY());
                  }
            }


            private void showPopup(int x, int y)
            {
                  // Popup anzeigen
            }
      }