Extract folder from git repository with history (and shrink repository)

Irgendwas ist ja immer, heute will ich aus einem GIT Repository (>200MB) ein Verzeichnis incl. der History extrahieren.

Mit Hilfe von Tante Google werde ich schnell fündig und lege los.

Repository clonen

git clone https://... repository

Branch umschreiben

git filter-branch --prune-empty --tag-name-filter cat --subdirectory-filter FOLDER-NAME -- --all

Mit dem folgenden Befehl schreiben wir jetzt die „Geschichte“ (History) um. Dabei ist folgendes zu beachten:

  • --prune-empty entfernt „leere“ Commits
  • --tag-name-filter cat aktualisiert ggf. Tags
  • -- --all als Basis nehme ich den aktuellen Branch, ich will keinen neuen anlegen

Repository aufräumen – Part I

Jetzt haben wir den aktuellen Branch so umgeschrieben, als wenn er schon immer den Inhalt das angegebenem Verzeichnisses hatte. Das Problem ist nur, die Dateien der anderen Branches, Tags usw. sind immer noch im Repository.

In den meisten HowTos im Netz kommen jetzt die folgenden Tipps:

  • Alle Branches, die man nicht brauch löschen
  • Alle remotes löschen
  • Das „reflog“ „expiren“: git reflog expire --expire=all --all
  • Aufräumen: git gc --aggressive --prune=now
  • schlechte Kommentare, wenn es dann doch nicht geht

Alle Tipps haben in meinem Fall viel Zeit gekostet, am Ende konnte ich das Repository nie auf eine vernünftige Größe bekommen.

Repository aufräumen – Part II

Wie häufig reicht es eben nicht aus, mit einem guten Halbwissen plus den Tipps von Tante Google los zu legen.

Als nächstes habe ich mir ein Zeit genommen, um zu verstehen, was git so alles in seinem Repository speichert und wie. Hier kann ich das Kapitel „Git Internals“ (10.1 – 10.5) empfehlen (Buch ProGit).

Weiterhin war die Doku zu dem Befehl git filter-branch extrem hilfreich. Im Abschnitt „CHECKLIST FOR SHRINKING A REPOSITORY“ habe ich dann gelesen und verstanden wie man ein Repository shrinken kann.

In meinem Fall hatte git filter-branch alle „alten“ Branches und Tags als Original in einem intern Folder gespeichert. So wurden alle alten Commits immer noch referenziert und git gc konnte nix aufräumen.

In meinem Fall habe ich mich für das Clonen entschieden und clone nur den „altuellen“ Branch.

git clone --single-branch --branch develop file:///path/to/repo  repositoy-clean

Auch hier war die original Dokumentation wieder extrem hilfreich: Beim Clonen die Quelle immer als URL, also mit dem Prefix file://… angeben — denn sonst macht git nur Hard-Links und man hat ein gleich großes Repository.

Als nächste habe ich mit den bekannten Mitteln das neue Repository wieder auf den Server geschoben und im alten das Verzeichnis gelöscht.

Zusammenfassung<

  • Mit Git kann jeder Folder, incl. seiner History, aus einem Repository extrahiert werden — ohne dass wir den ganzen Rest an Commits behalten müssen.
  • Und wieder habe ich etwas gelernt: Ich habe eine Ahnung, wie cool git seine interne Datenbank aufbaut und was daraus fürs Aufräumen resultiert.
  • Tante Google hat zwar viele Tipps parat (die leider in vielen Fällen keine Hintergrundinformationen bieten) — und deswegen will ich in Zukunft häufiger die Original-Dokumentation zu Rate ziehen.

Referenzen

  • https://git-scm.com/docs/git-filter-branch
  • https://git-scm.com/docs/git-clone
  • https://git-scm.com/book/en/v2
  • https://help.github.com/articles/splitting-a-subfolder-out-into-a-new-repository

Micro-Optimierung — bringt das immer was?

Irgendwas ist ja immer, heute Micro-Performance-Optimierung.

Man ließt immer mal wieder von den coolen Jungs, die mal wieder einen super schnellen Implementierung einer HashMap oder einer anderen JDK-Funktion implementiert haben. Wenn sie gut sind, haben sie ihre Performance Messungen auf unterschiedlichen Rechnern durchgeführt. Am besten noch mit JMH, einem für diese Zwecke speziell entwickeltes Framework — ich muss sagen, eine echt cooles Spielzeug, wenn man so etwas mag.

Ich muss zugeben, ich habe mich der Micro-Optimierung auch schon das eine oder andere mal hingegeben (als Fingerübung 🙂 ) um mich dann am Ende zu fragen: was hat das jetzt wirklich gebracht? Was sagen mir die Zahlen: vorher 163.840 Operationen pro Sekunden und nach der Optimierung, waren es 819.200 — Hammer — Faktor 5 — was für sind wir nicht für geiler Programmierer, oder? Aber was hat diese Optimierung für Auswirkungen auf mein Programm? Laufen meine Algorithmen jetzt 5 mal schneller? Wenn man die meisten Micro-Optimierungs-Blog-Artikel ließt glaubt man das sofort.

Als Beispiel nehmen wir eine Anwendung, die Dokumente erzeugt und wir die Operation für das Schreiben von Tags/Elementen wie oben erwähnt optimiert haben. Jedem ist sicherlich sofort klar, das wir jetzt nicht auf ein mal 5 mal mehr Dokumente verarbeiten können — noch wird das Erzeugen eines Dokuments 5 mal schneller sein.
Der erste Ansatz ist, die eingesparte Zeit pro einem mittel großen Dokuments zu berechnen. Bei einer Tag-Größe von 64 Zeichen und einer Dokumenten Größe von 1 MB benötigen wir 16.384 Operationen. Dann haben wir bei dieser Dokumenten-Größe sage und schreibe 0,08 Sekunden gespart — sofern ein Dokument von einem Thread bearbeitet wird. Ehrlich, da habe ich mir bei einem Faktor 5 ein bisschen mehr vorgestellt.

Beim zweiten Ansatz möchten wir wissen, wieviel mal man die Operation ausführen muss um eine Sekunde (eine Minute) an Laufzeit zu sparen — wir drehen den Spieß um. In unserem Fall sparen wir eine Sekunde bei 204.800 Ausführungen und für eine Ersparnis von einer Minuten müssten wir über 12 Millionen Tags schreiben oder umgerechnet 750 Dokumente. Das ganze lohnt leider nicht wirklich, denn die besagte Anwendung erzeugt pro Tag vielleicht mal 10 Dokumente.

Es gibt gerade wieder ein paar Performance Artikel im Eclipse Umfeld für die EclipseCollections. Wenn ich dort dann lese, dass das JDK 1 mio ops/s macht und die Eclipse Implementierung 3 mio op/s bin auch ich schwer beeindruckt. An dieser Stelle würde mich viel mehr interessieren, welche Auswirkungen das auf einen Full-Build hat oder für das Editor-Feeling. Um eine Sekunde schneller zu werden, braucht es 1,5 Mio Operations und für ne Minute 90 Mio pro Thread — kommt da ein Full-Build dran? Viel spannender würde ich es finden, wenn diese Optimierung es in eines der nächsten JDKs schaffen würden.

Weiter geht es mit if-else/for/while Optimierung um auch noch das letzte Stück Performance aus dem Code zu holen. Meistens ist er nach der Optimierung nicht mehr so gut lesbar, dafür konnte man aus 15 Mio 21 Mio Operationen pro Sekunde machen. Doof ist nur, das die Methode optimistisch 1 Mio mal pro Tag aufgerufen wird — wir brauchen aber 52 Mio Aufrufe um eine Sekunde zu sparen. Da Optimiere ich doch lieber den Code bei if/else zum besseren Verstehen und spendiere einer while Schleife einen extra Method-Call um diese hässlichen while ((line = reader.readLine()) != null) zu vermeiden.

In diesem Sinne happy coding.

Thoughtworks Technologie Radar May2018

Irgendwas ist ja immer, dieses mal bin ich wieder bei einem Thoughtworks Technology Radar Vortrag und wie immer war es sehr spannend. Hier meine persönlichen Highlights:

  • Lightweight-Architecture-Decision-Records
    Wir sollten mit einfachen Mitteln die Rahmenbedingungen für wichtigen Entscheidungen (und die Entscheidung selber) kurz und knapp in einem Source-Code-Verwaltungssystem dokumentieren. Wir Entwickler können dann schnell per IDE-Search nach Themen suchen und haben zusätzlich noch eine
    Historie parat, insbesondere dann, wenn ein paar Tage ins Land gezogen sind und neue Menschen im Projekt wissen wollen, warum man „damals“ sich für xyz entschieden hat.
  • ArchUnit
    Das Tool klingt spannend und scheint recht einfach in einen CI-Workflow integrierbar zu sein. Ich denke, ich schau mir das mal an.
  • nsp
    Im Javascript-Umfeld entstehen gefühlt im Minuten-Takt Sicherheitslücken und neue Versionen. In diesem Umfeld die Dependencies auf Stand zu halten ist eine Herkules-Aufgabe — ich hoffe nsp kann da Linderung verschaffen. Auch ein Kandidat zum angucken.
  • Hyperapp
    Mal wieder ein neues Javascript-Framework, soweit nix neues. Nur dieses passt in sage und schreibe 1KB. Da haben sich die Macher viel Mühe gemacht, denn es soll die wichtigsten Konzept der großen abbilden — da bin ich mal gespannt.
  • AssertJ
    Hamcrest ist schon ein bisschen in die Jahre gekommen und hat sich schon länger, also sehr lange, nicht bewegt. Die Thoughtworks-Zusammenfassen ließt sich spannend — ich denke, es lohnt sich mal ein Blick drauf zu werfen.
  • Blockchain und Etherium (ier nur eine Auswahl – ich hab ggf. noch was übersehen)
  • Und was war noch? Kubernetes

Referenzen:
* Thoughtworks-Technology-Radar

Interesting code: org.codehaus.plexus.util.FileUtils.forceDelete(File)

Interesting code: Today we have a look at org.codehaus.plexus.util.FileUtils.forceDelete(File).

FileUtils.foreceDelete is a very common helper function, found in nearly all util libs around the world. Why is this one from the plexus-utils project so interesting to have a detailed look into it? Short answer: it turns nearly all my Eclipse full builds into a CPU consuming long running task.

As mentioned forceDelete is very common and if you have look into the Appache commons-io project you will found the nearly same function — but a little bit different.

forceDelete in the commons-io

This code is from Appache commons-io:2.5 (newest version):

[code lang=java]
public static void forceDelete(File file) throws IOException {
if (file.isDirectory()) {
deleteDirectory(file);
} else {
boolean filePresent = file.exists();
if (!file.delete()) {
if (!filePresent){
throw new FileNotFoundException("File does not exist: " + file);
}
String message =
"Unable to delete file: " + file;
throw new IOException(message);
}
}
}
[/code]

forceDelete in the plexus-utils

An the next code is from the plexus-utils:3.0.24 project (newest version).

[code lang=java]
public static void forceDelete( final File file ) throws IOException {
if ( file.isDirectory() ) {
deleteDirectory( file );
} else {
/* NOTE: Always try to delete the file even if it appears to be non-existent.
* This will ensure that a symlink whose target does not exist is deleted, too. */
boolean filePresent = file.getCanonicalFile().exists();
if ( !deleteFile( file ) && filePresent ) {
final String message = "File " + file + " unable to be deleted.";
throw new IOException( message );
}
}
}
[/code]

On the first view both do the same:
* first check if the given file is a directory,
* second check if the file exists
* third delete the file
* last throw an exception, if the file could not be deleted.

But the plexus version version does not call the file.delete method, it calls a private delete(File) method. So we have a look at this method:

[code lang=java]
private static boolean deleteFile( File file ) throws IOException {
if ( file.isDirectory() ) {
throw new IOException( "File " + file + " isn't a file." );
}
if ( !file.delete() ) {
if ( Os.isFamily( Os.FAMILY_WINDOWS ) ) {
file = file.getCanonicalFile();
System.gc();
}
try {
Thread.sleep( 10 );
return file.delete();
} catch ( InterruptedException ignore ) {
return file.delete();
}
}
return true;
}
[/code]

This method does a lot of magic in case the file could not be deleted:
* first it starts a Garbage-Collection on a Windows system
* second it sleeps for 10 millis
* last: it just tries the delete a second time

Currently I have no idea, why a Garbage-Collection run and to wait 10 millis will change the result for the file.delete on any Windows-Systems. In case the file was opened by an other application we can nothing do in our application to solve the situation. If the file was opened in our own application, I suggest to just close the file.

In the past I came around a lot of situations, where we could not delete a file in our application and in all those situations it was always the same reason: some one did not close the stream and it was always a problem on Windows-Systems — we choose to fix the code to close the stream and get every file file.delete() deleted with any other magic 🙂 .

I assume the version of commons-io will just du the right things and suggest to remove all the additional stuff from the `plexis-utils‘ version.

forceDelete in the yuicompressor-maven-plugin

If you call this method for one or two files you will not be aware of the System.gc() call and of the 10 millis sleeping — but if your application used already 2 GB memory and you call it 1.000 or more times your application drastically slows down and the garbage collecter will consume 100% CPU.

This happened in the yuicompressor-maven-plugin, if you have a lot of *.css files and do your development with Eclipse on a Windows-System (Team members on Linux system did not have any problems.) — I assume it will slow down every IDE which uses this plugin.

Now we will have a look at the yuicompressor-maven-plugin code:

[code lang=java]
@Override
protected void processFile(SourceFile src) throws Exception {

InputStreamReader in = null;
OutputStreamWriter out = null;
File outFileTmp = new File(outFile.getAbsolutePath() + ".tmp");
FileUtils.forceDelete(outFileTmp);
try {
in = new InputStreamReader(new FileInputStream(inFile), encoding);

[/code]

The {{forceDelete}} is used to just make sure to have no old tmp files laying around. I suggest to just use a ‚better‘ forceDelete implementation or to put a file.exists before the forceDelete to workaround the „Windows-Problems“ of the original FileUtils.foreceDelete.

In my environment I just disable the yuicompressor-maven-plugin as quick fix and second I opend a bug on github and looking forward what happend. Stay tuned.

(1) https://github.com/codehaus-plexus/plexus-utils/issues/21

Interesting code: Today File.toURI vs. File.getAbsoluteFile

Interesting findings while profiling Eclipse

While profiling Eclipse on a Windows-System I came a round the File.toURI. Every profiler session this method shows up under the top 10 hotspot method, while I never found that method on a MAC system — profiling the same application with the same workspace.

  1. I checked how many times this method was called while compiling my workspace and it was round about 100,000 times.
  2. I dig into the code: toURI was called to calculate a string which is used in a cache (as far as I understand the code)

Benchmark File.toURI vs. File.getAbsoluteFile

With a simple test cases we can check the runtime for both variants on files and directories. For 100.000 calls I could not find a difference for getAbsoluteFile on both systems. For toURI it looks different: the Windows was 10 times slower.

toURI produced an absolute path to a file with a schema prefix — in general all files in a workspace are on the same filesystem with the same schema prefix, therefore I suggest: Just replace toURI().toString() to getAbsoluteFile().toString() and the cache will still work as expected, but it will be much faster on Windows systems.

Conclusion

Or in other words: keep things simple and use only the information you really need: if you do not need the schema information of an URI, do not use a URI in favor of getAbsoluteFile().

Second: Java programs run on any platform and performance issues should be tracked down on that system they are reported for — ok, if you hit a n^2 algorithms this will likely operating system in dependent :).

Remarks

  • The Windows system was a desktop machine with Windows 10 and SSD.
  • The MAC was a MacBook with 10.12.3 and SSD.
  • I did not publish run times, because the will differe on different system. A factor of 10 for the same base function on different systems with different underlaying file system is really interesting.
  • I checked also the toUri() and toAbsolutePath from the Path objects and get nearly the same results — no better performance on Windows.

Interesting code: Wy JDBC database driver do not register correctly

Interesting code: Wy database driver do not register correctly

Today I came around the static method java.sql.DriverManager.loadInitialDrivers which is called in the static initializer of DriverManager. It handles the automatic JDBC driver registration, which depends highly on the java.util.ServiceLoader class.
In short the DriverManger searches the classpath for META-INF/services/java.sql.Driverresources to force the load of the classes defined in the found resources. This is done by the following code found in loadInitialDrivers

[code lang=java]
try{
while(driversIterator.hasNext()) {
driversIterator.next();
}
} catch(Throwable t) {
// Do nothing
}
[/code]

In the past this works in nearly every environment with all kind of JARs on the classpath. But today it fails
with a simple exception:

SQLException: No suitable driver found for …

It took me some time to find out why:
On the classpath a JAR shows up, which introduced a META-INF/services/java.sql.Driver containing a class, which static initializer throws a what every exception. The problem is: every exception which is thrown in the while will leave the loop — pending resource will not be evaluated and all pending JDBC driver will not get registered.

I found the following comment before this loop:

Load these drivers, so that they can be instantiated.
It may be the case that the driver class may not be there
i.e. there may be a packaged driver with the service class
as implementation of java.sql.Driver but the actual class
may be missing. In that case a java.util.ServiceConfigurationError
will be thrown at runtime by the VM trying to locate
and load the service.

Adding a try catch block to catch those runtime errors
if driver not available in classpath but it’s
packaged as service and that service is there in classpath.

Nice try, because in those cases no other driver will be registered, because the first which fails will exit the loop.

@JDK-Developer: Can we just put the try...catch into the loop
@JDBC-Driver-Developer: Please do not throw any exception during your static initializer.

JDK: 1.8.0_101 Mac

Mit AddblockPlus Web-Seiten optimieren

Irgendwas ist ja immer, heute ist es eine nervende Sidebar auf einer großen Webseite.

Das Problem

Jedes mal wenn man sich auf der Seite eine Anzeige anschaut oder aber eine Suche abändert kommt eine Sidebar á la Karl Klammer ins Bild:

  • Sie schieb sich ins Bild
  • schlägt irgendwas wichtiges vor und
  • verschwindet erst wieder wenn ich sie aktiv weggeklickt habe

Ich empfinde es als störend bei der Bedienung der Web-Seite, weil ich immer wieder aus meinem eigenen „Workflow“ gerissen werde. Grundsätzlich finde ich die vorgeschlagene Funktion gut, nur nervt es, wenn man immer wieder dran erinnert wird.
Leider reagiert der Support von XYZ mit Unverständnis, sodass ich mich gezwungen sehe, selber kreativ zu werden — frei nach dem Motto: Wer bin ich, es nicht zu tuen.

Die kreative Lösung: AddblockPlus.

Das Plugin tut einen echt guten Dienst (ok, es gibt seit kurzer Zeit Web-Seiten, die ich angeblich nicht mehr sehen kann — ist mir bis dato noch nicht aufgefallen). Mit Firebug bekommt man schnell einen Überblick über die Web-Seite und mit ein bisschen AddblockPlus Dokumentation ist schnell der folgenden Filter geschrieben:

[code lang=css]
xxx.de##div[class="slider"]
[/code]

In AddblockPlus eine neue Filtergruppe anlegen und den oben genannten Filter hinzufügen. Das ganze funktioniert einwandfrei.

Ausblick

Das ganze funktioniert auch auf anderen Seiten. Sie versuchen immer wieder Dinge, die uns nicht interessieren, sehr prominent zu platzieren. Mit AddblockPlus können wir solche Elemente elegant ohne großen Aufwand verschwinden lassen. Schon macht es wieder Spass auf diesen Seiten zu surfen.

Plugin für XJC, Teil 2

Plugin für vereinfachte getter/setter in generierten JAXB Klassen

Problembeschreibung

Bei der Benutzung von generierten JAXB-Klassen kann es in einigen Situationen zu recht umständlichen getter/setter Konstruktionen kommen. Als Beispiel soll der folgende Auszug aus einer XSD dienen:

[code language=“xml“]
&lt;xsd:complexType name="Bestellung"&gt;
&lt;xsd:sequence&gt;
&lt;xsd:element name="BestellNummer" type="BestellNummer" /&gt;
&lt;xsd:element name="ArtikelNummern"&gt;
&lt;xsd:complexType&gt;
&lt;xsd:sequence&gt;
&lt;xsd:element name="ArtikelNummer" type="xsd:string" minOccurs="1" maxOccurs="unbounded" /&gt;
&lt;/xsd:sequence&gt;
&lt;/xsd:complexType&gt;
&lt;/xsd:element&gt;
&lt;/xsd:sequence&gt;
&lt;/xsd:complexType&gt;
&lt;xsd:complexType name="ArtikelNummer"&gt;
&lt;xsd:attribute name="value" type="xsd:string" /&gt;
&lt;/xsd:complexType&gt;
[/code]

Das setzen der Artikelnummer gestaltete sich dann wie folgt:

[code language=“java“]
BestellNummer nummer = new BestellNummer();
nummer.setValue("foobar");
bestellung.setBestellNummer(nummer);
[/code]

und das Auslesen ist auch nicht einfacher:

[code language=“java“]
String bestNummer = null;
BestellNummer nummer = bestellung.getBestellNummer();
if (nummer != null) {
bestNummer = nummer.getValue();
}
[/code]

Schöner wäre es, wenn man den Wert direkt auslesen bzw. setzen kann:

[code language=“java“]
bestellung.setBestellNummer_("foobar");
bestNummer = bestellung.getBestellNummer_();
[/code]

Dafür müssen in der entsprechenden JAXB-Klasse folgenden Methoden vorhanden sein:

[code language=“java“]
public String getBestellNummer_() {
if (this.bestellNummer == null) {
return null;
} else {
return this.bestellNummer.getValue();
}
}
public void setBestellNummer_(String value) {
if (value == null) {
this.bestellNummer = null;
} else {
this.bestellNummer = new BestellNummer();
this.bestellNummer.setValue(value);
}
}
[/code]

Damit man diese Methoden nicht immer bei einer Schema-Generierung verloren gehen, werden sie von einem eigenen Plugin einfach mit generiert. Nach schöner wäre es, wenn wir die Erzeugung des Attributes plus Getter/Setter Methoden anpassen. Das XJC bietet dafür in Ansätzen einen Mechanismus, der aber ist leider nicht vernünftigt umgesetzt, denn die wichtigen Funktionen sind entweder private (z.b. das Erzeugen des richtigen getter Namens) oder aber wichtige Klassen haben nur package visibility und erzeugen schon im Konstruktor Code, sodass man nicht wirklich etwas anpassen kann. Bleibt nur noch die zusätzliche Erzeugung von gettern/settern (damit wir keinen Namenskonflikt bekommen, bekommen die Methoden ein „_“ ans Ende). Soweit die Vorrede.

Algorithmus

Für alle Attribute einer Klasse, die

  • als Typ eine von uns generierte Klasse haben (Target-Type)
  • deren Target-Typ genau ein Attribut (Target-Attribut) besitzt

generieren wir:

  • eine optimierte setter-Methode, wenn wir eine setter-Methode in der Target-Type-Klasse zum passenden Target-Attribut finden
  • eine optiierte getter-Methode, wenn wir eine getter-Methode in der Target-Type-Klasse zum passenden Target-Attribut finden

Helferlein

Als erstes nehmen wir das Plugin-Gerüst aus dem letzten Blog. Dann brauchen wir noch ein paar Helferlein, um unseren Algorithmus besser programmieren zu können.

Die folgende Methode berechnet das Prefix für die Getter-Methoden. Der Quelltext wurde inspiriert von der XJC Implementierung, sodass beide das gleiche Verhalten zeigen (schön wäre eine einheitliche Hilfsmethode gewesen, die konnte ich aber leider nicht finden).

[code language=“java“]
protected String getterPrefix(JType type) {
boolean useIs = false;
if (options.enableIntrospection) {
useIs = type.isPrimitive() == true &amp;&amp; codeModel.BOOLEAN.equals(type.boxify().getPrimitiveType());
} else {
useIs = codeModel.BOOLEAN.equals(type.boxify().getPrimitiveType());
}
return (useIs == true ? "is" : "get");
}
[/code]

Wir wollen für alle Klassen, die genau ein Attribut haben „vereinfachte“ getter/setter erstellen, sodass wir für eine generierte Klasse prüfen müssen, ob diese genau ein Feld besitzt. im XJC werden generierte Klassen durch JDefinedClass dargestellt. Damit ist es einfach die einzige definierte Klassen-Variable herauszubekommen. Sollte die Klasse mehrere Variablen beinhalten geben wir null zurück.

[code language=“java“]
protected JFieldVar getSingleField(JDefinedClass cls) {
final Map&lt;String, JFieldVar&gt; map = cls.fields();
if (map.size() != 1) {
return null;
} else {
return map.values().iterator().next();
}
}
[/code]

Als nächstes benötigen wir zu einem Attribute einer Klasse, den jeweiligen Typ des Attributes. In XJC werden die Attribute einer Klasse als FieldOutline dargestellt. Zu einem Field müssen wir nun den entsprechenden Java-Typ heraus finden. Dabei interessieren wir uns nur für von XJC erzeugte Klassen. Für alle anderen Klassen wollen wir den Zugriff nicht vereinfachen. Finden wir keine Klasse, geben wir wieder null zurück.

[code language=“java“]
protected JDefinedClass mapFieldToDefinedClass(FieldOutline field) {
if (field.getRawType() instanceof JDefinedClass) {
return (JDefinedClass) field.getRawType();
}
return null;
}
[/code]

Zu einem FieldOutline benötigen wir die zugehörige Definition in der erzeugten Java Klasse. Diese suchen wir über den Namen des Felds. Sollten wir keine zugehörige Field-Definition finden, oder der Type stimmt nicht überein, geben wir null zurück.

[code language=“java“]
protected JFieldVar mapToFieldVar(FieldOutline field) {
Map&lt;String, JFieldVar&gt; map = field.parent().implClass.fields();
JFieldVar fieldVar = map.get(field.getPropertyInfo().getName(false));
if (fieldVar.type().equals(field.getRawType()) == true) {
return fieldVar;
} else {
return null;
}
}
[/code]

Als letztes benötigen wir noch eine Methode mit der wir die getter bzw. setter zu einem Feld innerhalb einer Java-Klasse finden können. Zum Suchen benötigen wir die Klasse, in der wir suchen wollen, als JDefinedClass, ein Prefix mit der die zu suchende Methode anfangen soll, der Rückgabe-Type als JType und die Signatur als JType[]. Wir geben nur dann einen JMethod zurück, wenn wir genau eine passende Methode finden. Ansonsten geben wir null zurück.

[code language=“java“]
protected JMethod searchMethod(JDefinedClass cls, String prefix, JType returnType, JType[] signatur) {
JMethod found = null;
for (JMethod m : cls.methods()) {
if (m.name().startsWith(prefix) == true // assume only the starting name
&amp;&amp; m.type().equals(returnType) == true //
&amp;&amp; m.hasSignature(signatur) == true) {
if (found != null) {
return null; // if we can not find a unique getter for the field, should never happen
} else {
found = m;
}
}
} // for
return found;
}
[/code]

Herzstück

Jetzt haben wir alle Bausteine zusammen mit der wir unsere optimierten getter/setter-Methoden erzeugen können. Wir werden jetzt die run Methode erweitern, sodass wir versuchen für alle uns bekannten Felder die optimierten Methoden zu erzeugen. Dafür rufen wir die process-Methode auf allen Feldern aller Klassen auf.

[code language=“java“]
public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException {
System.out.println("run " + getOptionName() + " …");
this.codeModel = outline.getCodeModel();
this.options = opt;
for (ClassOutline cls : outline.getClasses()) {
for (FieldOutline field : cls.getDeclaredFields()) {
process( field);
}
}
System.out.println("run " + getOptionName() + " … done");
return true;
}
[/code]

Die process-Methode fürht jetzt folgende Schritte durch:
1) wir suchen eine Feld-Definition für das übergeben Feld, sollten wir keine finden können wir keine Methoden erzeugen

[code language=“java“]
JFieldVar $property = mapToFieldVar(field);
if ($property == null) {
return; // stop if we could not find a local field for given field outline
}
[/code]

2) dann ermitteln wir den Typen des gefunden Felds. Wir können nur weiter arbeiten, wenn der Type eine von uns erzeugte Klasse ist.

[code language=“java“]
JDefinedClass jTargetCls = mapFieldToDefinedClass(field);
if (jTargetCls == null) {
return; // stop if target class is not a generated one
}
[/code]

3) dann überprüfen wir, ob der gefunden Typ genau eine lokale Variable enthält. Sollte das nicht der Fall sein, können keine optimierten getter/setter erzeugt werden:

[code language=“java“]
final JFieldVar jFieldVar = getSingleField(jTargetCls);
if (jFieldVar == null) {
return; // no optimized getter if we do not have a unique target field
}
[/code]

4) damit eine optimierte getter-Methode erzeugt werden kann, müssen wir die entsprechende getter Methode auf dem Typen des lokalen Properties finden. Find wir eine, werden wir eine optimierte getter-Methode erzeugen

[code language=“java“]
JMethod targetGetter = searchMethod(jTargetCls, getterPrefix(jFieldVar.type()), jFieldVar.type(), null);
if (targetGetter != null) {
// if( this.property == null ) {
// return null;
// } else {
// return this.property.getV();
// }
JType type = targetGetter.type();
JMethod getter = field.parent().implClass.method(JMod.PUBLIC, type, getterPrefix(jTargetCls)
+ field.getPropertyInfo().getName(true) + "_");

JConditional $if2 = getter.body()._if(JExpr._this().ref($property).eq(JExpr._null()));
$if2._then() //
._return(JExpr._null());
$if2._else() //
._return(JExpr._this().ref($property).invoke(targetGetter));
} // endif getter
[/code]

5) für die optimierte setter-MEthode machen wir das gleiche

[code language=“java“]
JMethod targetSetter = searchMethod(jTargetCls, "set", codeModel.VOID, new JType[] { jFieldVar.type() });
if (targetSetter != null) {
// if( value == null ) {
// this.propert = null;
// } else {
// this.property = new …()
// this.property.set….(value)
// }
JType type = targetSetter.params().get(0).type();
JMethod setter = field.parent().implClass.method(JMod.PUBLIC, Void.TYPE, "set"
+ field.getPropertyInfo().getName(true) + "_");

JVar $newValue = setter.param(type, "value");
JConditional $if = setter.body()._if($newValue.eq(JExpr._null()));
$if._then() //
.assign(JExpr._this().ref($property), JExpr._null());
$if._else() //
.assign(JExpr._this().ref($property), JExpr._new(field.getRawType())) //
.invoke(JExpr._this().ref($property), targetSetter).arg($newValue);
} // endif setter
[/code]

Die so generierten setter/getter erlauben einen direkten Zugriff auf das Klassen-Feld der Ziel-Klasse. Auch werden beim Setzen die entsprechenden „Zwischen-Klassen“ erzeugt bzw. gelöscht. Beim Zugrif auf Listen wird nur eine optimierte getter-Methode erzeugt.

Als kleinen Nebeneffekt lernt man mit diesem Beispiel noch das Erzeugen von Java-Quelltext mit dem internen Java CodeModel, welches in der JAXB-Distribution sehr gut per JavaDoc dokumentiert ist.

Weitere Referenzen:

Plugin für XJC, Teil 1

Plugin schreiben und starten:

Als erstes benötigen wir ein Plugin, dass bei der Code-Generierung des XJC-Tool aufgerufen wird. Das Plugin muss von der Klasse com.sun.tools.internal.xjc.Plugin erben. Unsere erste Version sieht wie folgt aus:

Unser erstes Plugin muss die folgenden Methoden implementieren:

  • getOptionName: diese Methode muss einen eindeutigen Namen für das Plugin zurück geben. Mittels diesem Namen kann das Plugin über die Komandozeile des XJC enabled bzw. disabled werden.
  • getUsage: Mit diese Methode gibt einen Hilfetext zurück, die beim Aufruf des XJC mit dem Parameter -help ausgegeben werden. Es werden keine weiteren Parameter unterstützt, sodass wir uns damit begnügen dem Benutzer zu sagen, wie er das Plugin enablen kann.
  • run: diese Methode wird während der Code-Generierung ausgeführt und ermöglicht das Verändern des erzeugten Code.

[code language=“java“]
package de.kue.sandbox.xjc;

import com.sun.tools.internal.xjc.Options;
import com.sun.tools.internal.xjc.Plugin;
import com.sun.tools.internal.xjc.outline.Outline;

public class PluginImpl extends Plugin {

@Override
public String getOptionName() {
return "Xcodeplugin";
}

@Override
public String getUsage() {
return "\t-Xcodeplugin\tenable";
}

@Override
public boolean run(Outline outline, Options opt, ErrorHandler errorHandler) throws SAXException {
System.out.println("run " + getOptionName() + " …");
System.out.println("run " + getOptionName() + " … done");
return true;
}
}
[/code]
Als nächstes müssen wir dem XJC-Tool das neue Plugin „unterjubeln“. Das XJC-Tool nutzt den ServiceLocator vom Java um neue Plugins zu finden. Aus diesem Grund muss das neue Plugin entsprechend angemeldet werden. Dafür wird im JAR die folgende Datei mit dem entsprechenden Pfad angelegt.

META-INF/services/com.sun.tools.internal.xjc.Plugin

Die Datei enthält pro Zeile den vollqualifizierten Pfad der neuen Plugins. In unserem Fall ist das der Name de.kue.sandbox.xjc.PluginImpl .
Als nächstes muss das JAR, welches das Plugin und die Service-Datei enthält, zum Classpath des XJC hinzugefügt werden.
Bei einem xjc -help sollte jetzt der oben angegebene Hilfetext zu lesen sein und bei der Code-Generierung sollte die Zeilen

run Xcodeplugin…
run Xcodeplugin… done

auftauchen, wenn man xjc -Xcodeplugin ... nutzt. Soweit so gut, der Teufel steckt im Detail.

Bei der Benutzung des JDK’s:

  • Das JDK hat eine eigene JAXB-Implementierung, die andere Packages verwendet als Standalone-Version. Bei der Entwicklung muss man das entsprechende tools.jar in den Classpath aufnehmen. Dort findet man dann die benötigten Klassen in einem Package mit dem Namen com.sun.tools.internal.xjc.*.
  • Das Plugin sollte mit dem gleichen JDK compiliert werden zu dem auch das XJC gehört. Damit ist man auf der sichereren Seite.
  • Auf einem Apple mit dem JDK 7 klappt das wie folgt: xjc -cp codeplugin.jar -help

Bei der Benutzung des Standalone JAXB Distribution:

  • Ich habe es nach zahllosen Versuchen und extrem debugging im XJC-Tool aufgegeben. Aus welchen Gründen auch immer werden die Klassen der JAXB Distribution mit einem anderen Classloader geladen als meine Plugin-Klasse und damit kann der Java ServiceLocator kein Plugin finden.
  • Christian Ullenboom konnte Licht ins Dunkel bringen: das Problem haben schon x andere gehabt: http://java.net/projects/jaxb/lists/users/archive/2005-06/message/6 und da die Antwort http://java.net/projects/jaxb/lists/users/archive/2005-06/message/12 . Danke für die spontane Klärung.

Bei der Benutzung von Maven/Ant:

  • Hier muss ich auf die entsprechend Doku der jeweiligen Plugins/Tasks verweisen. Dort wird beschrieben, wie man das erzeugte JAR in den Classpath des XJC-Tools bekommt.
  • Hier gilt aber auch, aufpassen bei den Namen der Packages und dem erzeugten Byte-Code.
  • Und soweit ich das sehen kann, gibt es auch hier Classloader-Problem.

Mit den Hilfen sollte es jetzt einfacher sein, das Plugin zum laufen zu bekommen. Ich habe es nur mit dem internen JDK 7 JAXB zum Laufen bekommen, aus diesem Grund nutze ich die „internal“ Packages.

Weitere Referenzen: