Für internationalisierte (I10N) Implementierungen verwende ich gerne das CAL10N API. Das API erreicht eine Compiler Assisted Localization durch den Einsatz von Java enums. Der Name des Enumerationsfeldes ist der Schlüssel in einer entsprechenden Properties Datei. Hierdurch wird der Schlüssel innerhalb des Java-Code nicht mehr als String hinterlegt. Durch ein Maven Plugin können die Properties-Dateien überprüft werden. Ich empfehle jedem Java Entwickler sich das API einmal anzuschauen.
Ich habe den Ansatz für den Einsatz in meinen privaten Projekten und bei bei Zimory erweitert. Durch den Einsatz von enums können auch Annotationen zum Einsatz kommen. Zwei Annotationen habe ich definiert. Eine für den enum Typen und eine für die enum Felder. Beide haben als einzigen Wert einen String:
@Target(TYPE)
@Retention(RUNTIME)
public @interface SystemId {
String value();
}
@Target(FIELD)
@Retention(RUNTIME)
public @interface MessageId {
String value();
}
@SystemId definiert grobgranular das Modul/Subsystem – oder wie auch immer die Systemunterteilung namentlich genannt wird – für die Messages. @MessageId definiert eine spezielle feingranulare ID für z.B. einen Fehlerzustand innerhalb eines Moduls. Der Sourcecode einer Message enum sieht dann in etwa so aus:
import ch.qos.cal10n.LocaleData;
import ch.qos.cal10n.Locale;
import ch.qos.cal10n.BaseName;
@SystemId("RT")
@BaseName("messages")
@LocaleData({@Locale("en")})
public enum Messages {
/** Indicates a login failure. */
@MessageId("001001") LOGIN_VALIDATION_FAILED;
}
Für die Auswertung haben ich einen eigenen MessageProducer geschrieben. Dieser kapselt eine Implementierung des CAL10Ns IMessageConveyor Interface. Ausserdem analyisert er per Reflection die Annotationen und baut aus den Werten eine ID zusammen. Diese ID wird vor die internationalisierte Nachricht gestellt. Daneben übernimmt die Implementierung noch einige Dinge wie Caching usw. Eine erzeugte Nachricht sieht in etwa wie die Folgende aus:
RT-001001: Login failed for user "foobar".
Solche IDs sind nicht nur für die Analyse von Fehlermeldungen sinnvoll. Sie bieten auch einen einfachen Ansatz für die Fehlerpropagierung in Systemen, in denen bisher kein durchgängiges Exceptionframework existierte. Damit auch nicht Entwickler die Fehlermeldungen verstehen, haben ich eine Doclet-Implementierung geschrieben. Diese analyisert alle enum Klassen mit @SystemId und generiert aus dem Wert der Annotation, der Javadoc Klassendokumentation, dem Wert von @MessageId und der Javadoc Felddokumentation eine XML Datei mit einer Zusammenfassung der Daten. Durch einfache XSLT Skripte wird das XML Dokument in Docbook und Xdoc umgewandelt und kann somit für die Anwenderdokumentation weiter verarbeitet werden. Da alles aus einer Quelle kommt, werden Fehler durch cut 'n paste reduziert.
Voll reingelaufen. Priorität: low, no workaround. Und ich muss jetzt eine Lösung finden ![]()
Money Quote:
Introspecting returns incorrect PropertyDescriptor for overriden getter in case when child getter has more specific return type. In this case PropertyDescriptor of parent class is returned instead.
Bis Java 1.6.0_18 kein Fix. In Java 7 (b80) gefixt. Leider kann ich mir dafür gar nichts kaufen.
Für meinen Job bei Zimory habe ein wenig mit Weld SE gespielt. Weld ist die CDI Referenzimplementierung und Weld SE eine Anpassung für die Java SE Welt.
Die eigentliche Implementierung war recht einfach. Allerdings wurde beim Ausführen immer eine UnsatisfiedResolutionException geworfen. Die Nachricht war WELD-001308 Unable to resolve managed beans for…
. Nach kurzer Suche war klar, dass Weld eine leere "META-INF/beans.xml" Datei benötigt. Nachdem die Datei angelegt war, funktionierte das Beispiel problemlos.
Es lässt sich streiten, ob die CDI Referenzdokument in Kapitel 12.1 eine solche Datei zwingend vorschreibt. Ich habe es dort nicht heraus gelesen.
Download: Sourcecode (7.897 Bytes)
javac scheint bei package Annotationen nicht zu überprüfen, ob die Annotationen den deklarierten Regeln folgen. Eben habe ich einer solchen Annotation den default Wert weggenommen. Die Verwendung auf type Ebene warf Compiler Fehler. Ist der Wert in der package-info.java Datei nicht vorhanden, läuft der compile Prozess ohne Probleme durch. Die falsche Verwendung auf package Ebene wurde durch nicht angepassten Unit-Test entdeckt. Eine IncompleteAnnotationException wird geworfen, wenn ich den Wert über das Reflection API auslese.
Vorher:
@Documented
@Retention(RUNTIME)
@Target({PACKAGE, TYPE})
public @interface CommandHolder {
String value() default "";
}
Nacher:
@Documented
@Retention(RUNTIME)
@Target({PACKAGE, TYPE})
public @interface CommandHolder {
String value();
}
Verhalten tritt auf mit javac 1.6.0_16 und javac 1.7.0-ea (b80) auf einem aktuellen AMD64 Ubuntu 9.10.
Vielleicht wühle ich mich später mal durch die Spezifikationen.
Ich implementiere gerade privat ein REST API. In dem API werden XML Daten mittels JAXB gewandelt. Dabei habe ich einfach POJOs verwendet und mit den JAXB Annotationen versehen. Eigentlich ganz einfach. Eigentlich… Bis zu dem Zeitpunkt an denen Collections ins Spiel kommen. Naiv wie ich war – he, wer liesst schon Spezifikationen – habe ich Collection Properties einfach annotiert. Und weil ich ein pfiffiger Entwickler bin, habe ich bei den Gettern eine leere Collection (Collections.emptyXyz()) zurück gegeben, wenn die Referenz null war. Ergebniss: unmarshall(…) warf eine UnsupportedOperationException. Das Problem: JAXB erwartet eine mutable Collection. Collections.emptyXyz() liefert aber immutable Objekte. JAXB ruft darauf dann clean() auf und füllt die Collection neu – die Refernzimplementierung mittels add(E). Eine null Referenz wird auch nicht akzeptiert und führt ebenfalls zu einer Ausnahme.
Ich habe mir dann doch mal die JAXB 2.2 Spezifikation von 2009-12-10 durchgelesen. Unter Punkt 5.5.2.2 wird das Verhalten spezifiziert. Mit einer Einschränkung: sie gilt nur für Collections vom Typ List. Die JAXB 2.2 Referenzimplementierung kommt aber auch mit Sets und dem Collection Interface selbst zurecht.
Der Code sieht jetzt so aus:
@Valid
@NotNull
@Size(min=0, max=5)
@OneToMany(fetch=EAGER)
@XmlElement(name=ANSWER_XML_NAME)
@XmlElementWrapper(name=POSSIBLE_ANSWERS_XML_NAME)
public List<Answer> getPossibleAnswers() {
if (this.possibleAnswers != null) {
return this.possibleAnswers;
}
return new ArrayList();
}
Wem JAD nicht mehr genügt, weil er nur bis Java 1.4 sinnvoll decompiliert (kein Generics Support), mag sich JD anschauen. Kein Problem mit inneren Klassen, synthetischen oder Brigde-Methoden/Typen. Annotationen werden aufgelöst und enum Typen decompiliert. Der erzeugte Quellcode ist gut lesbar - ist ja der kniffelige Part bei Decompilern. Wird bei mir JAD ablösen.
Und ich frage mich, warum die Decompiler Jungs immer C++ verwenden.
Manchmal muss es ein grober Klotz sein. Ein Klasse die ich entwickelt hatte, hatte eine bestimmte Signatur. Nix wildes, beispielsweise war die Klasse final. Wenn eine Klasse final ist, muss ich mich nicht um die Probleme kümmern, die Vererbung mit sich bringen kann. Das final war plötzlich verschwunden. Grund: in einem Test wurde eine abgeleitete Version benötigt. Leider wurde nur das final entfernt. Die Klasse aber nicht hinsichtlich Vererbung angepasst. Z.B. habe ich in der equals Methode noch den instanceof Operator verwendet. Geht bei final Klassen. Bei Klassen, die nicht final sind ist das grob fahrlässig.
Ich habe den Test umgeschrieben – es geht auch einfach ohne Vererbung mit nur einer Zeile Code mehr und dann die Tests erweitert. Bisher habe ich immer nur die Erzeugung eines Exemplars über den Konstruktor getestet. Jetzt teste ich auch die Signatur. Den Test habe ich mit einem Kommentar versehen. Hoffentlich hilft es.
@Test
public void creation() {
// If you must change these tests go into yourself and check:
// are the changes are really needed?
new Notifier();
final Constructor>[] cons = Notifier.class.getDeclaredConstructors();
assertThat(cons.length, is(equalTo(1)));
assertThat(Modifier.isPublic(cons[0].getModifiers()), is(equalTo(true)));
final int modifiers = Notifier.class.getModifiers();
assertThat(Modifier.isPublic(modifiers), is(equalTo(false)));
// Implementation safe for inheritance?
assertThat(Modifier.isFinal(modifiers), is(equalTo(true)));
}
Vorher sah der Test so aus:
@Test
public void creation() {
new Notifier();
}
In Beispielen für Datenübertragungen findet sich häufig eine Serialisierung der Daten in eine XML Applikation. Dieser Eintrag soll zeigen, dass XML nicht immer die erste Wahl sein muss. Es soll das Bewusstsein erweitern nicht immer das Erstbeste zu verwenden, was einem in den Sinn kommt.
Für das Sportics Realtime Telemetry API wurde bewusst auf XML verzichtet. Der Grund: Realtime Telemetry Clients arbeiten auf reduzierter Hardware wie z.B. Mobiltelefonen und haben eine instabile Funkanbindung des Netzwerkes (z.B. Sportics J2ME Applikation). In solchen Umgebungen ist XML nicht die erste Wahl. Die Daten werden nicht kompakt genug repräsentiert.
Ein Realtime Telemetry Datensatz besteht aus einer Menge von Messwerten zu einem bestimmten Zeitpunkt. Ein Messwert hat immer auch eine beschreibende Kennung. Mehrere solcher Datensätze werden zu einem Snap zusammengeführt. Ein Realtime Telemetry Client schickt einen solchen Snap z.B. alle 2 Minuten an den Realtime Telemetry Server.
In XML kann ein solcher Datensatz wie folgt dargestellt werden:
<data>
<measurements>
<measurement type='longitude'>12.5675256</measurement>
<measurement type='time'>123456789</measurement>
<measurement type='heartrate'>78</measurement>
</measurements>
<measurements>
<measurement type='longitude'>12.5675456</measurement>
<measurement type='time'>123457813</measurement>
<measurement type='heartrate'>79</measurement>
</measurements>
...
</data>
Es fällt auf, dass ein solches Format sehr viel redundanten Overhead produziert. Dieser kann durch kürzere Namen reduziert werden. Dabei geht allerdings die sprachliche Wiedererkennung verloren.
<data>
<ms>
<m t='lon'>12.5675256</m>
<m t='tme'>123456789</m>
<m t='hrt'>78</m>
</ms>
<ms>
<m t='lon'>12.5675456</m>
<m t='tme'>123457813</m>
<m t='hrt'>79</m>
</ms>
...
</data>
Kompakter, aber auch schwieriger zu lesen. Es ist im Übrigen ein Irrglaube, dass das JSON Format kompakter ist. In der obigen Notation ist es grösser.
Eine weitere Möglichkeit der Reduktion ist auf allgemeine Namen für XML Elemente zu verzichten. An deren Stelle wird der Wert des t-Attributes verwendet:
<data>
<ms>
<lon>12.5675256</lon>
<tme>123456789</tme>
<hrt>78</hrt>
</ms>
...
</data>
Ein ungewöhnliche Schreibweise, aber durchaus machbar. Bei diesem Aufbau wird ein entsprechendes JSON Dokument im Übrigen kleiner als sein XML Pendant. Hierzu folgt weiter unten eine Grafik aus realen Messwerten.
Es geht noch kompakter. Bei Sportics wurde bei der Realtime Telemetry API bewusst gegen SOAP Webservices entscheiden. Der Grund ist einfach: nicht alle Zielgeräte verfügen über SOAP Webservices Support. SOAP Webservices von Hand nachzuprogrammieren ist keine Kleinigkeit. Stattdessen haben wir uns für ein REST API entschieden. Dies vereinfachte die Kommunikationsschnittstelle und auch den Aufwand für die Implementierung auf Clientseite. Zwar wird auch in der REST Literatur viel mit JSON und XML gearbeitet, aber dies muss nicht sein.
Bei den Messwerten setzen wir statt dessen auf das CSV-Format nach RFC 4180. Im CSV-Format wird der oben beschriebene Datensatz wie folgt notiert:
tme,lon,hrt
123456789,12.5675256,78
123457813,12.5675456,79
Noch kompakter ist kaum möglich. Das API schreibt noch einige Dinge vor, beispielsweise muss ein Header vorhanden sein und jede Zeile nach der Headerzeile muss so viele Spalten haben wie die Headerzeile. Ansonsten verwirft der Server die Daten und liefert eine Fehlermeldung.
Die abschliessende Frage lautet: Um wieviel kleiner ist die CSV-Notation?
Aus den realen Daten von einigen tausend Datensätzen lässt sich folgendes Bild gegenüber der CSV Notation ermitteln:
- die XML Notation ist um ca. den Faktor 2 grösser
- die JSON Notation ist um den Faktor 1,5 grösser
In absoluten Zahlen:
| CSV | XML | JSON |
|---|---|---|
| 2.791.662 | 5.831.791 | 4.361.462 |
Ein Bild beschreibt mehr als 500 Worte:
Dieser Inhalt wurde aus einer weiteren Quelle zusammengefügt. Mehr...
- Cost cutter - süchtig machender Tetris Clone.
via Petronella - Weiss gar nicht wieso sich die Sozialverbände und -anbieter so über die geplante Reduzierung der Wehrdienstzeit aufregen. Zivis sollen keine Arbeitsplätze wegnehmen dürfen, also nur Jobs machen, auf die verzichtet werden kann. Wie schlimm steht es eigentlich ums Pflegesystem?
- Android Icons für die Entwicklung unter Creative Commons Attribution Share Alike Lizenz.
- Some Java Concurrency Tips
Auf http://code.google.com/p/sportics-j2me habe ich die Applikation, die beim Frankfurter Helbmarathon gelaufen ist, hochgeladen. Der Code ist unter der GPL 3 freigegeben. Die Version 1.0 ist mit dem Nokia N79 und N85 getestet worden und lief 5 Stunden stabil. Dann war der Akku leer ![]()
In meiner spärlichen Freizeit entwickel ich gerade eine mobile Anwendung. Für J2ME. Wer in mir jetzt einen Masochisten sieht. Nur zu, du hast nicht ganz unrecht.
Das Problem: Ich muss kurze Informationen über Position, Zeit und einiges mehr ausgeben. Der Benutzer muss diese Information unter Stress schnell erfassen können. Die J2ME Plattform ist in dieser Hinsicht nicht zu gebrauchen. Deswegen habe ich ein eigenen UI entwickelt. Für das UI habe ich mit dem MicroFont-Maker den DejaVu True Type Font die benötigten Bilder erzeugt. Nachteil: DejaVu sieht auf einem Handydisplay nicht wirklich gut aus. Ausserdem ist er zu breit. Das heisst, Information nimmt zuviel Platz ein. Bei eingeschränken Handy Displays nicht optimal.
Heute hatte ich dann genug von DejaVu. Ich machte mich auf die Suche nach einem Ersatz. Anforderung: Gut auf Handy Displays aussehen und unter einer freien Lizenz stehen. Die Lösung ist naheliegend: Das Android Projekt hat genau die gleichen Probleme. Und es hat eine Lösung: Droid. Ein Font für limitierte Displays unter der Apache 2.0 Lizenz.
Das Ergebnis ist überzeugend. Der Font sieht aus dem Handy eleganter aus und ist im Schnitt 20% schmaler. Droid hat heute DejaVu abgeöst.
Nein, nix über Snow Leopard. Nur die Bestätigung das Max Recht hat. Mac OS X wird immer mehr zu Windows: jedes Popelupdate erfordert einen Neustart. Heute ein lächerliches 161 MB Java-Update. Wieso erfordert ein Java-Update einen Neustart? Das versteht vermutlich nur Apple.
Im just on the trip to simplify some aspects of software development thru bytecode extendsion. In «Loggen vereinfachen» (german) I've written down some thought about simplify logging.
Today I read a blog entry from Kirk Pepperdine on the subject. The tipp is trivial. Kirk suggests that before a logcall a condition checks whether the call is at all meaningful. The approach makes sense, but has a nasty side effect: it bloats the code in such a way that the actual business logic between the log blocks will eventually go away.
One solution may be to examine the generated bytecode. If there is a log call in the code, it can be automatically surrounded with such a if statement.
Advantage: The developer can continue to write simple log calls without bloat the code.
Disadvantage: It does not look right, a tool that manipulates its code. An he needs the tool.
What prevails? Advantage or disadvantage? In my opinion, the advantage of cleaner code.
As well as some Java developers read along I would be interested what you think.
Für meinen Arbeitgeber, die Zimory GmbH, habe ich einen Prozessor für die @ToString Annotation implementiert.
equals und hashCode
Was können jetzt die nächsten Schritte sein? Als Erstes fällt natürlich ein ähnlicher Mechanismus für equals und hashCode ein. Hierfür gibt es aber schon hervorragende Unterstützung in den IDEs. Ebenfalls muss für dieses beiden Methoden der Sourcecode angepasst werden. Grund: equals und hashCode können gravierdende Auswirkungen auf ein System haben. Deswegen muss in den Javadocs dokumentiert sein, dass das die beiden Methoden überschrieben wurden. Das Problem könnte mit Annotationen und dem Annotation Processing Tool umgesetzt werden.
Loggen
Mit einer einheitlichen toString() lässt sich aber auch das Loggen stärker vereinfachen und vereinheitlichen. Loggen ist ein klassisches Cross-Cutting Concern. Es hat im Code fachlich selten etwas zu suchen. Dadurch, dass es gemacht wird, bläht es den Code wiederum unfachlich auf. Ergebniss: irgendwann sieht der Entwickler vor lauter Log-Anweisungen das fachliche nicht mehr.
Eine @Log Annotation kann da weiter helfen. Sie kann auf Konstruktoren, Methoden und lokalen Variablen angewendet werden. Bei Konstruktoren und Methoden erzeugt ein Prozessor beim Ein- bzw. Austritt Logmeldungen. Bei Eintrittmeldungen können die eventuell übergebenen Parameter ausgegeben werden und/oder der aktuelle Zustand des Objektes. Bei Austrittsmeldungen eventuell der Rückgabewert und auch der aktuelle Zustand des Objektes.
Lokale Variablen
Bei lokalen Variablen kann es Sinn machen, wenn die Methoden grösser sind. Eigentlich bin ich ein Freund von Composed method and SLAP. Deswegen muss loggen von lokalen Variablen nicht wirklich sein. Dennoch kann es machmal sinnvoll sein.
Aktuelle Grenzen
Java setzt hier allerdings Grenzen. Zwar können lokale Variablen annotiert werden, die Information geht aber beim Compilieren verloren. Sie geht auch verloren, wenn die RetentionPolicy auf CLASS gesetzt ist (default). Der Grund bei Java 5 war, dass keine Struktur im Classfile-Format für lokale Annotationen definiert war. In Java 6 können lokale Annotationen theoretisch verwendet werden, allerdings wurde die Funktionaliät im Compiler ausgeschaltet. Der Compiler des JSR 308 (downalod ) Projektes erzeugt die nötigen Informationen. Mal sehen ob der Compiler es in Java 7 schafft. Bis es soweit ist, schiebe ich das Problem beiseite.
Wieso nicht den Quellcode modifizieren?
Macht es Sinn den Quellcode zu parsen und den benötgen Log-Code für lokale Annotationen einzuweben? Soetwas kann gemacht werden. Allerdings unterscheidet sich dann der erzeugte Quellcode wahrscheinlich hinsichtlich Zeilenposition. Dies wiederum ist unschön bei Stacktraces, wenn Entwickler Zeilennummern bekommen. Dies haben keine sinnvolle Aussagekraft mehr und sind wertlos. Da das Logging zusätzlich für die Fehlersuche herangezogen wird, kann eine Quellcodemodifikation hier kontraproduktiv wirken. Aus diesem Grund ziehe ich sie nicht weiter in Betracht.
Ausblick
Im nächsten Eintrag diskutiere ich dann, wie die Annotationen für ein vereinfachtes Logging aussehen müssen. Ebenfalls wird diskutiert, welche Logging-Frameworks wie unterstützt werden und welche Probleme es dabei gibt.
KML ist nicht die eleganteste XML Applikation, aber eine effektive. Mit JAK gibt es jetzt ein API zur einfacheren Verarbeitung.
Die @ToString Java Annotation benötigt Werkzeugunterstützung, damit sie irgend einen Sinn macht. Ich hatte hier schon eine Beispielimplementierung verlinkt. Die Implementierung zeigt nur, wie es geht und das es geht. Sie ist weit von Vollständigkeit entfernt. Beispielsweise behandelt sie nicht alle Eventualitäten und skaliert auch nicht hinsichtlich Werkzeugunterstützung.
Werkzeugunterstützung
Die Werkzeugunterstützung soll sich nicht nur auf ein Frontend beschränken. Ich selbst bevorzuge Maven, kann aber die Vorbehalte dagegen nachvollziehen. Deswegen sollen auch Ant, Gradle oder Buildr User sollen unterstützt werden. Deswegen wird der eigentliche @ToString Prozessor von der @ToString Implementierung und den Frontends getrennt.
Die Trennung zwischen der @ToString Annotation und dem Prozessor wird vorgenommen, da
- der Prozessor nichts in der Anwendung selbst zu suchen hat
- es weitere Prozessorimplementierungen geben kann
Der Prozessor
Der Prozessor muss zwei Dinge erledigen:
- den Bytecode von Klassen untersuchen, um die
@ToStringAnnotationen aufzusammeln - aus den aufgesammelten
@ToStringAnnotationsinformationen gegebenenfalls einetoString()Methode in den untersuchten Bytecode einfügen
Analyse
Der Prozessor analysiert den Bytecode. Dies ist mit Unterstützung von Werkzeugen wie ASM trivial. Anders als bei einer Analyse mittels Reflection muss die Vererbungshierarchie allerdings manuell untersucht werden. Bei Reflection Analysen hat sich in anderen Projekten gezeigt, dass es öfter zu ExceptionInInitializerError kommt als gedacht. Insbesondere bei grösseren Projekten, in denen auch unerfahrene Entwickler mitarbeiten, ist dies ein Phänomen.
Die Analyse der Vererbung muss berücksichtigen, dass private Methoden und Felder in Oberklassen nicht berücksichtigt werden können. Bei package-private Feldern und Methoden muss geprüft werden, ob sich die Ober- bzw. Unterklasse im selben Package befindet. Ist dies der Fall, dürfen sie ausgewertet werden. Ist es nicht der Fall, so dürfen sie – wie bei private – nicht berücksichtigt werden.
protected und public Felder und Methoden sind kein Problem.
Das Analysewerkzeug muss auch statische und/oder finale Methoden und Felder berücksichtigen. Wie diese zu behandeln sind, wird dann später bei der toString() Methodenerzeugung berücksichtigt.
Erzeugung
Der Prozessor muss einfach sein und eine robuste toString() Methode implementieren. Hierbei wird stark auf Java Bordmittel zurückgegriffen. Innerhalb der toString() Implementierung wird ein StringBuilder verwendet. Mittels dessen append Methoden werden die Werte von Feldern und Methoden aneinandergereit.
Zu beachten ist, dass StringBuilder keine Methoden für das Anhängen der primitiven Typen byte und short hat. Hier würde es beim Laden des Bytecodes zu einem verify-Fehler kommen. Mit byte und short muss deswegen die entsprechende int Methode aufgerufen werden. Ebenfalls sollte die append(char[]) des StringBuilders nicht aufgerufen werden. Grund: es sollen die Werte des Arrays dargestellt werden, nicht das Array als String.
Der Rückgriff auf den StringBuilder und die Arrays Hilfsklasse, die im folgenden Abschnitt erläutert wird, befreien auch von null Referenz Prüfung. Diese Aufgabe übernehmen diese beiden Systemklassen mit ihren Methoden.
Arrays
Für die Arrayunterstützung bietet Java mit der Klasse Arrays einige fundamentale Hilfsmethoden an. Uns interessieren dabei aktuell die toString(…) Methoden. Diese Methoden erzeugen aus einem eindimensionalen Array einen definierten String. Für mehrdimensionale Arrays steht die deepToString(Objet[]) Methode bereit. Der erzeugende Code muss also untersuchen, ob es sich um ein eindimensionales Array oder um ein mehrdimensionales Array handelt und entsprechend reagieren.
enum, Interfaces und Annotationen
Grundsätzlich ist es verboten Methoden in Java Interfaces oder Java Annotationen zu erzeugen. Der Classloader würde soetwas mit einem VerifyError quitieren und die entsprechende Klasse nicht laden.
Auf bei Enums sollte keine toString() Methode erzeugt werden, da diese bereits definiert ist. Der Prozessor soll also Enums, Interfaces und Annotationen ignorieren. Im Bytecode ist hinterlegt, um welchen Typen es sich handelt.
toString() Methode vorhanden?
Wird im Bytecode eine toString() Methode mit der entsprechenden Signatur gefunden, dann muss entschieden werden was zu tun ist. Ein konservativer Prozessor soll die bestehende Methode nicht ersetzen. Dennoch soll er so zu konfigurieren sein, dass er die bestehende Methode entfernt und eine eigene Methode erzeugt.
Ist keine toString() Methode vorhanden, dann wird eine toString() Methode erzeugt. In einem solchen Fall kann davon ausgegangen werden, das keine toString() Methode im Quelltext vorhanden ist. Deswegen muss der Prozessor die zu erzeugende Methode als synthetisch kennzeichnen. Dies schreibt Kaptitel 4.7.6 der Java Virtual Machine Specification (Second Edition) vor.
Anders verhält es sich bei einer Ersetzung der toString() Methode. Dabei kann davon ausgegangen werden, dass die toString() Methode im Quellcode vorhanden ist.
Abschluss
Wenn ich nichts übersehen habe, dann kann die Implementierung jetzt starten.
Vor ein paar Tagen hatte ich dieses Problem. Ich habe es am Wocheende radikal gelöst. Die Java Annotation @ToString kann vor Felder und Methoden ohne Parameter gesetzt werden. Ein Prozessor liesst die Informationen aus und injiziert eine Java toString() Methode in den Bytecode. Ein Prototyp kann hier herunter geladen werden. Der Prototyp ist noch sehr rudimentär und prüft noch nicht alle Eventualitäten, berücksichtigt aber Vererbung (nicht jedoch Interfaces). Der injizierende Code funktioniert. Nächster Schritt dann, ein Tool drum basteln und ein entsprechendes Maven Plugin schreiben.
Es ist nicht der erste Code, der soetwas macht. Beispielsweise beschreibt Dennis Sosnoski auf IBM Developer Works etwas ähnliches.
I prefer flat key-value based configuration structures. They are not domain specific and can be widely used in different development environments. Also flat key-value based configuration structures are widley used in a lot of commercial project. But they have some disadvantages. The development team must maintain a central document, where the keys are stored. It always happens that developer forget to keep documentatiom up to date. Sourcecode documentation like javadoc is a poor administrator documentation, even for bigger projects with a bunch of moduls. As a solution, the development team must maintain the semantic documentation of the key-value pair away from the sourcecode. Maybe in a wiki or other technical documentations sheets.
A solution may be the metadata programming model of languages like Java (Annotation since Java 5) or C# (System.Attribute). In the following, I describe a simple system to get out of the dilemma, based on my experience with Java. The idiom is implemented in 5 different projects – only the hibernating εos-toolkit is open source. At this time there are no negative side effects.
I define the configuration keys in the sourcecode as String constants. Other types are also possible, but the make no sense. Also none constant fields make no sense. Constants in Java are static final fields. The may be public, private or protected. The public visibility has less side effects as you'll see later. The value of the constant defines the configuration key. To avaoid naming conflicts, I prefer full qualified names.
Example:
public class ConstantExample {
public static final String CONFIGURATION_KEY =
ConstantExample.class.getName() + ".something";
}
In the root package (namespace), the value is "ConstantExample.something". This technique is safe enough in order to avoid naming conflicts. The constant may be refered somewhere in the sourcecode to access the configured value. Its out of scope of this articel how to do that.
In the εos-tooklkit project I have defined a annotation to mark a constant as configuration key. The annotation contains also some information to document the semantic of the configuration key, supporting an optional default value, a type of the value and an optional predicate to check the value of the configuration key. In the εos-toolkit project the documentation is also optional. But it is always a good idea to make the documentation mandatory.
The current implementation:
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Documented
@Retention(value = RUNTIME)
@Target(value = FIELD)
public @interface ConfigurationKey {
String description();
String defaultValue() default "";
Type type() default Type.STRING;
Class<? extends Predicate<String>>[] validators() default AlwaysTruePredicate.class;
public enum Type {
BOOLEAN,
INTEGER,
FLOAT,
STRING,
CLASSNAME
}
}
The annotation field definitions are marked. If you don't understand the annotations of the annotation definition, please refer the Javadoc pages. The Retention was set to RUNTIME to get the default value at runtime if no value is configured (e.g. null). Cause of this the key constant should also be public.
The usage is quite easy:
public class ConstantExample {
@ConfigurationKey(description = "Something is a simple random value.")
public static final String CONFIGURATION_KEY =
ConstantExample.class.getName() + ".something";
}
The constant field is now marked as a configuration key. The other possible settings are:
defaultValue-
During my software development experience I've found, that many configuration values have a meaningful value. With this field it is possible to define such a default value. In a further artcile I'll describe a utility class to lookup the configuration value in the from the configuration system or returns the default value if the configuration system does not contain such a value.
If the developer does not define a default value, the value is an empty
String. type-
Defines the possible type of the configuration value. The field is for configuration tool support. Based of the value a configuration tool can supply spezialized widgets for entering configuration values.
The defined
enums above are only a recommendation of possible values. Also possible and used are the typeCLASSNAMEfor factory configurations orDATEfor a⦠yes date or IP4 and so on. validators-
Defines a list of
Predicateclass that validates the configuration value. This part is also to support configuration tools. APredicaterecieves a value, validates it and returns only atrueorfalse. The implementation should support a default constructor.If the developer does not support a
Predicateor reuse an existing one, the defalut used implementation should always returntrue.
More complex example:
public class Ip4SubnetConfigurator {
@ConfigurationKey(description = "Useable IP4 net.",
defaultValue = "10.0.0.10/20",
type = IP4,
validators = {Ip4NetPredicate.class})
public static final String CONFIGURATION_KEY =
Ip4SubnetConfigurator.class.getName() + ".start.subnet.ip4";
}
Currently there are no other useful values known. An experiment with a module definition has been identified as not useful. The module can be represented with the package or JAR. Also in an upcomming blog entry, the module is represented by the Maven artifact identifier.
With special tools, it is now possible, to introspect a bulk off classes, to generate a key-value file with the configuration values. Its also possible generate a file for configure a system and to generate documentation pages in APT, Docbook or DITA. It's also possible to supply the value directly during a nightly build via Webservices to an existing wiki system like Cobertura or Trac.
Conclusion
The ConfigurationKey annotation is a simple mechanism to mark keys for key-value configuration systems. With tools it's easy to collect them back and aggregate them. No centralized repository (e.g. wiki) are needed to document the keys.
Upcomming
Tool support with Maven.
Bei Nokia wurde die Gängelung des Benutzers besonders weit getrieben. Wo kommen wir den hin, wenn Programme flüssig zu bedienen sind. Mit einem selbsterstellten Zertifikat (ohne geht gar nix) fragt die Applikation beispielsweise: das Programm möchte eine Dateioperation ausführen. «Das Programm möchte Verzeichnis X lesen. Erlauben sie dies?» Ja. «Das Programm möchte Datei A bearbeiten. Erlauben sie dies?» Ja. «Das Programm möchte in Datei A Daten schreiben. Erlauben sie dies?» Ja.
So geht der Bockmist in einer Tour weiter. Das Menü, in welchem eingestellt werden kann, dass das Programm nicht zu lästig wird: tief in den Eingeweiden des S60 Systems verborgen (bei S40 gibt es keinerlei Einstellmöglichkeiten). Allein: bei den Dateioperationen kann nur ganz verboten oder immer fragen eingestellt werden. Was für ein Usability-GAU.
Warum kann das System nicht einfach einmal fragen: vertrauen sie diesem Zertifkat? Ja, Nein, weiss nicht. Bei Desktopsystemen funktioniert das doch auch. Abgesehen von der Tatsache, das da draussen sowieso 99% der Benutzer mit dem ganzen Zertifikateschnickschnack nichts anfangen können. Da wird immer auf «Ja» geklickt.
Und jetzt kommt bloss keiner und sagt: schaff dir doch ein Zertifikat von Verisign oder wem auch immer an. Für ein Programm, dass ich nur privat benutze?
