30.07.2017 - hashCode()/equals() mit Lambdas

Korrekte hashCode() und equals() Implementierungen sind sehr wichtig, aber es macht keinen Spaß, sie zu schreiben. Glücklicherweise generieren moderne IDEs die Methoden automatisch, also muss man nicht zu viele Gedanken daran verschwenden. Leider hat dieser Ansatz zwei Nachteile:

  1. in eigentlich sehr einfachen Klassen (e.g. DTOs or JPA entities) entstehen mindestens 20 Zeilen komplex wirkenden Codes, der zum Einen die Lesbarkeit reduziert und bei statischer Code Analyse negativ auffallen kann
  2. jede IDE generiert die Implementierung etwas anders, daher unterscheidet sich der Code z.B. Eclipse und IntelliJ

Lambdas als Alternative

Eine Alternative, um das Generieren der hashCode() und equals() Methoden zu vereinfachen, sind die mit Java 8 eingeführten Lambdas. Ich habe mir also folgenden Ansatz überlegt:

public class HashCodeEqualsBuilder {

    public static  int buildHashCode(T t, Function<? super T, ?>... functions) {
        final int prime = 31;
        int result = 1;
        for (Function<? super T, ?> f : functions) {
            Object o = f.apply(t);
            result = prime * result + ((o == null) ? 0 : o.hashCode());
        }
        return result;
    }

    public static  boolean isEqual(T t, Object obj, Function<? super T, ?>... functions) {
        if (t == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (t.getClass() != obj.getClass()) {
            return false;
        }
        T other = (T) obj;
        for (Function<? super T, ?> f : functions) {
            Object own = f.apply(t);
            Object foreign = f.apply(other);
            if (own == null) {
                if (foreign != null) {
                    return false;
                }
            } else if (!own.equals(foreign)) {
                return false;
            }
        }
        return true;
    }
}

Diese Hilfklasse kann wie folgt bei der Implementierung einer Klasse (z.B. ein DTO) verwendet werden:

@Override
public int hashCode() {
    return HashCodeEqualsBuilder.buildHashCode(this, MyClass::getA, MyClass::getB);
}

@Override
public boolean equals(Object obj) {
    return HashCodeEqualsBuilder.isEqual(this, obj, MyClass::getA, MyClass::getB);
}

Basierend auf einigen Microbenchmarks, unterscheidet sich dieser Ansatz nicht messbar zu einer autogenerierten Implementation (z.B. von Eclipse). Ich werde zukünftig diesen Ansatz verwenden und kann ihn auch nur wärmstens empfehlen::

  1. es werden keine unnötigen Code-Zeilen zum Projekt hinzugefügt, die evtl. noch negativ in der Code Coverage durch Unit Tests bewertet werden
  2. eine Änderung an der Implementierung (falls je notwendig) muss an exakt einer Stelle durchgeführt werden

08.11.2015 - How to use the JPA Criteria API in a subquery on an ElementCollection

Imagine the following entities:

class Category {
    private String softKey;
}

class Item {
    @ElementCollection
    private Collection<String> categorySoftKeys;
}

If we now would want to load only the non-empty categories, our JPQL would look like this:

EntityManager em = getEntityManager();
List<Category> categories = em.createQuery("select c " + 
    "from Category c " + 
    "where softKey in (select i.categorySoftKeys from Item i)", Category.class)
    .getResultList();

How do we do that in Hibernate using the JPA criteria API?

// Create the outer query for categories
EntityManager em = getEntityManager();
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Category> query = builder.createQuery(Category.class);
Root<Category> from = query.from(Category.class);

// Create the subquery for items using a category
Subquery<String> subquery = query.subquery(String.class);
Root<Item> subfrom = subquery.from(Item.class);
subquery.select(subfrom.join(Item_.categorySoftKeys));

// Apply the subquery to the query
query.where(from.get(Category_.softKey).in(subquery));

// Load the categories
TypedQuery<Category> q = em.createQuery(query);
List<Category> categories = q.getResultList();

Both solutions return the same result. Note that the first one is way less code, while the second one can properly react to if and provide (partial) compile time validation when used in conjunction with the hibernate metamodel generator.

The key to making this work using the criteria API is to understand the difference between Root#get() and Root#join(). The former one will just create a new path, while the later one performs an inner join. The scenario above would not work if you would have used:

subquery.select(subfrom.get(Item_.categorySoftKeys));

In this case hibernate generates invalid SQL.

15.03.2015 - Bootstrap Navigationstabs mit AngularJS als aktiv markieren

Wenn man eine neue Technologie wie AngularJS ausprobiert, sollte man mit einer möglichst einfachen Aufgabe beginnen. Yeoman erstellt bereits das notwendige Codegerüst für uns, aber es erstellt ein bootstrap-basiertes Menü, dass das Home-Element immer als aktiv markiert (zumindest in der aktuell verfügbaren Version 0.11.1). Dieses kleine Tutorial erklärt wie man den Code anpasst, um diese einfache Funktion zu implementieren.

Beginnen wir mit yeoman

Für dieses Tutorial ist es notwendig, dass du bereits Node.js und npm kennst und installiert hast. Wir starten also mit der Installation von yeoman:

mkdir bootstrap-navigation-example
cd bootstrap-navigation-example
npm install generator-angular

Der nächste Schritt ist das entsprechende Codegerüst für eine AngularJS-basierte App zu erstellen

yo angular
? Would you like to use Sass (with Compass)? Yes
? Would you like to include Bootstrap? Yes
? Would you like to use the Sass version of Bootstrap? Yes
? Which modules would you like to include? (Press  to select)
❯◉ angular-animate.js
 ◯ angular-aria.js
 ◉ angular-cookies.js
 ◉ angular-resource.js
 ◯ angular-messages.js
 ◉ angular-route.js
 ◉ angular-sanitize.js
 ◉ angular-touch.js

Anschließend kannst du dir die neu erstelle App mit folgendem Befehl ansehen:

grunt serve

Beim Anklicken von „About“ wirst du die About-Seite unter /#/about sehen (das passiert dank angular-route), aber das CSS wird weiterhin den „Home“-Tab als aktiv markieren.

Fehlende Hervorhebung

Das Menü reparieren

Zuerst müssen wir die problematische Stelle im Code finden. Wir öffnen also die Datei app/index.html und sehen uns die bootstrap Navigationsleiste an:

<div id="js-navbar-collapse" class="collapse navbar-collapse">
 <ul class="nav navbar-nav">
  <li class="active"><a href="#/">Home </a></li>
  <li><a href="#/about">About </a></li>
  <li><a href="#/">Contact </a></li>
 </ul>
</div>

Wie leicht zu sehen ist, ist für das li-Element „Home“ der CSS-Stil „active“ fest eingetragen. Wir wollen diese Logik jetzt durch einigen AngularJS-basierten JavaScript-Code ersetzen. Da wir eine CSS-Klasse setzen wollen, werden wir das Attribut „ng-class“ verwenden.

<div id="js-navbar-collapse" class="collapse navbar-collapse" ng-controller="HeaderCtrl">
 <ul class="nav navbar-nav">
  <li ng-class="{ active: isActive('/')}"><a href="#/">Home</a></li>
  <li ng-class="{ active: isActive('/about')}"><a href="#/about">About</a></li>
  <li><a href="#/">Contact</a></li>
 </ul>
</div>

Wir haben jetzt also folgendes geändert: Zum Einen haben wir einen ng-controller mit dem Namen „HeaderCtrl“ zum Menü hinzugefügt, zum Anderen haben wir eine ng-class zu den Menüelementen angegeben. (Hinweis: Wir lassen in diesem Tutorial „Contact“ absichtlich außen vor, da yeoman weder eine angular-route, noch einen Controller noch eine View für die „Contact“-Seite erstellt). Anschließen wollen wir natürlich den passenden Controller erstellen, um den Ausdruck in ng-class auswerten zu können. Wir fügen diesen neuen Controller in app/scripts/app.js ein (Es gibt einige Tutorials, die den Controller in der controller.js hinzufügen. Die ist aber nicht der Fall für eine App erstellt mit yeoman). Wir fügen also folgenden Code in das AngularJS-Modul am Ende der Datei ein:

    .controller('HeaderCtrl', function ($scope, $location) { 
        $scope.isActive = function (viewLocation) { 
            return viewLocation === $location.path();
        };
    });

Wenn du jetzt zurück in den Browser wechselst (und „grunt serve“ bereits die Änderung erkannt hat), wirst du sehen, dass das About-Tab als aktiv markiert ist.

Korrekte Hervorhebung

Ergebnis

Jetzt hast du gesehen, wie einfach es ist, mit AngularJS die ersten Schritte zu gehen und wie leicht du neue Funktionen in deine Applikation einbauen kannst. Nutze dieses Wissen und erstelle deine eigene Webapplikation.

15.03.2015 - Bootstrap Navigationstabs mit AngularJS als aktiv markieren

When you start playing around with AngularJS, you want to start with the most easy task. Yeoman does most of the hard word generating the necessary boilerplate code for you. But it generates a bootstrap menu that hardwires the “Home” tab as active (at least in the currently available version 0.11.1). This small tutorial will explain you how to adapt the code to add that simple feature.

Starting with yeoman

This tutorial assumes that you already have Node.js and npm up and running. First of all you need to install yeoman:

mkdir bootstrap-navigation-example
cd bootstrap-navigation-example
npm install generator-angular

The next step is to generate the boilerplate code for an angular based app

yo angular
? Would you like to use Sass (with Compass)? Yes
? Would you like to include Bootstrap? Yes
? Would you like to use the Sass version of Bootstrap? Yes
? Which modules would you like to include? (Press  to select)
❯◉ angular-animate.js
 ◯ angular-aria.js
 ◉ angular-cookies.js
 ◉ angular-resource.js
 ◯ angular-messages.js
 ◉ angular-route.js
 ◉ angular-sanitize.js
 ◉ angular-touch.js

After doing so you can start testing you newly bootstrapped app by running:

grunt serve

Once you click on “About” you will be sent to /#/about (by angular-route), but the CSS will still highlight “Home” as active.

Fehlende Hervorhebung

Fixing up the content

First of all we must find the problematic piece of code. You have to take a look at app/index.html and look at the bootstrap navigation bar:

<div id="js-navbar-collapse" class="collapse navbar-collapse">
 <ul class="nav navbar-nav">
  <li class="active"><a href="#/">Home </a></li>
  <li><a href="#/about">About </a></li>
  <li><a href="#/">Contact </a></li>
 </ul>
</div>

As you see the li-item of Home is hardcoded to have the CSS style active. Now we have to replace it by some JavaScript dynamic provided by AngularJS. As it is a CSS class being set, we want to use “ng-class” for the dynamic.

<div id="js-navbar-collapse" class="collapse navbar-collapse" ng-controller="HeaderCtrl">
 <ul class="nav navbar-nav">
  <li ng-class="{ active: isActive('/')}"><a href="#/">Home</a></li>
  <li ng-class="{ active: isActive('/about')}"><a href="#/about">About</a></li>
  <li><a href="#/">Contact</a></li>
 </ul>
</div>

What we did was adding an ng-controller named “HeaderCtrl” to the menu and also ng-class to the menu elements. (Note that we ignore Contact here, as the yeoman generated app does not generate a angular-route, controller and view for the Contact page). Now we need to create the according controller to evaluate the ng-class expression. This is done by adding a new controller in app/scripts/app.js (there are lots of tutorials telling you to change controller.js, but this does not match the boilerplate generated by yeoman). You need to add the following code to your angular module at the bottom of the file:

    .controller('HeaderCtrl', function ($scope, $location) { 
        $scope.isActive = function (viewLocation) { 
            return viewLocation === $location.path();
        };
    });

Now if you switch back to your browser (and grunt serve already picked up your changes) you will see that about is marked as active.

Korrekte Hervorhebung

Conclusion

Now that you’ve seen how easy it is to start of with AngularJS and how easy can features can be added, go ahead and create your own web application.

24.03.2014 - Scrum from a management point of view

I recently had a discussion with a friend of mine regarding the usefulness of Scrum. I’ve taken some helpful hints from the discussion on how a developer can feel used and misused by Scrum. Let’s talk about a few of them, but let’s start with some naked rules of Scrum first:

  1. The daily work of a developer is bound to the user stories of a sprint.
  2. Due to the nature of time only a limited amount of this resource can be spent during a sprint.
  3. The customer is expecting a result according to his wishes in the shortest time possible.

Theory: Scrum stops creativity

I can agree with this only partially. The truth is: The customer is setting the requirements and, most important, he pays for reaching them. That means: he is setting the direction where the project is heading, no matter how strange some requirements might appear to the developer. But of course that does not mean everyone should blindly follow a wrong direction. So what happens if a developer has a really good idea on how to improve the product? It’s really simple: Take the input from your developers and take it to the customer. If the idea is really that good, he will agree on implementing it. From my point of view the creativity is not harmed by Scrum in any way. Everyone can still suggest ideas, but you should not go ahead an implement them without permission of the customer. Next to that: It’s hard to plan when a developer might have that one brilliant idea.

Theory: There is no time to do things right

I can not agree with this at all. First of all it is really to find a proper definition of right. What is the right way to implement a search functionality? Performing a LIKE-search on a database column? Or use weighted search on a lucene index using a solr cluster? Both attempts have obvious advantages and disadvantages. A search using an index is likely to be more natural to the end user, but it might not even make a difference. And searching a small table with a couple of lines can perform really well, but what about millions of lines?

To me this boils down to one question: How good are my user stories? When a story is well defined (specifying the use cases, the amount of data and the likelihood of usage) it is easy to find the right solution. If it’s not a developer might go for a technically interesting but expensive solution. If a story is well defined, the estimated development time will be large enough give the developer enough room to implement a solution properly. If it’s not be prepared for bad code.

Theory: Implementations must be shortsighted

I partially agree with this. One of Scrums basic ideas is: „only implement what you really need“. In most cases a developer will have to focus on a single feature/problem without having the complete spectrum of future requirements in mind. In most cases this results in solutions very specific to the given problem.

Theory: No time to undo mistakes of the past

I largely agree with this. When a developer is working on a new feature it commonly happens that he will go through a lot of existing code. And every code base has established concepts of doing things and these are accepted by the developers. But from time to time some concepts have proven to be bad and need to be reworked. But the customer will not be very happy when, after months and years of work, being presented a product that looks exactly the same as before. It’s almost impossible to find a customer paying for this! So if your developers are completely used by the Scrum there is no time to fix the so called technical debt. In my experience it is good to stop the Scrum process from time to time to allow the developers to perform larger cleanups. Alternatively it also works to have a sprint without enough user stories to consume the whole time available.

Conculsion

I agree that a developer might feel controlled by the customer/product owner/scrum master and being less able to establish his own ideas. But in my experience that often boils down to improper usage of Scrum. Having good user stories and a bidirectional communication with all involved parties not only leads to better products, but most important to happier developers. When everyone involved in the Scrum process is fulfilling the required role, it can be an enjoyable development experience for all sides.

10.07.2013 - Einen Stuhl in Blender modellieren

Heute war mir danach … in Blender einen Stuhl zu modellieren! Hier sieht man das virtuelle Ergebnis. Wenn ich jetzt nur noch wüsste, wie man aus Holz einen Stuhl baut 😉

Model of a chair

Das Modell wurde vollständig in Blender erstellt und mit Cycles gerendert.

28.11.2012 - Anatomie eines git Commit-Kommentars

Jeder, der mit dem Versionskontrollsystem git zu tun hat, wird früher oder später feststellen, wie ein Commit-Kommentar aussehen sollte. Für alle, die das bisher noch nicht wissen, hier mal ein kurzes Beispiel:

Kurzer Betreff, max. 80 Zeichen

Optionaler längerer Text, der den Commit beschreibt. Dieser darf gern mehrzeilg
sein und die Beweggründe erläutern.

Optionale Signaturen.

Nun stellt sich die Frage: Warum macht man das so? Dies möchte ich anhand eines praktischen Beispiels erläutern:

mesa: remove '(void) k' lines

Serves no purpose as the k parameter is used later in the code.

In diesem Commit wurden mehrere Codezeilen gelöscht. Hätte der Autor gar keinen Kommentar angegeben, wäre rätselhaft warum er dies getan hat. Glücklicherweise erzwingt git immer eine Commit-Meldung. Aber auch die erste Zeile alleine ist nicht aussagekräftig, sondern folgt dem „stating the obvious“-Prinzip. Man findet damit den Commit leicht wieder, aber man kennt immer noch nicht den Grund. Dies wird erst durch die letzte Zeile klar.

Signaturen

Es gibt in git die Möglichkeit Signaturen in einer Commit-Meldung anzugeben, z.B.:

Signed-off-by: Christoph Brill <webmaster@egore911.de>

Diese dienen dazu, zusätzliche Informationen über die an dem Commit beteiligten Personen anzugeben. Es gibt keinen offiziellen Standard für diese Signaturen, aber folgende haben sich in verschiedenen Projekten als hilfreich erwiesen:

  • Signed-off-by: Der Commit wurde durch den Signierenden in die Repository aufgenommen. Diese Meldung dient mehr oder weniger als zusätzliche Autorenangabe
  • Reviewed-by: Der Commit wurde vom Signierenden durchgesehen und für sinnvoll befunden
  • Tested-by: Der Signierende hat den Commit in seinem lokalen Sourcecode eingespielt und getestet.

Verwendung im Alltag

In einer idealen Welt würde jeder detaillierte Kommentare (sowohl im Code als auch in Commit-Meldungen) verwenden. Die Praxis zeigt jedoch, dass dies nicht immer zutrifft. Selbst disziplinierte Projekte werden Commits aufweisen, die weit von diesem Ideal abweichen. Git (wie andere Versionskontrollsysteme auch) erlaubt es allerdings mit minimal strukturierten Commit-Meldungen anderen Entwicklern (oder dem Entwickler selbst zu einem späteren Zeitpunkt) den Commit zu verstehen, ohne jede Zeile Code des Commit gelesen zu haben.

12.11.2012 - NullPointerException beim Starten von JSF 2

Die Exception

Im Zuge der Umstellung auf JSF 2.x sind mir einige Exceptions begegnet. Eine davon ist relativ trickreich zu beheben. Sie äußert sich dass beim Deployment der Anwendung auf dem Server folgende Exception auftritt:

java.lang.NullPointerException
  at com.sun.faces.config.InitFacesContext.cleanupInitMaps(InitFacesContext.java:278)
  at com.sun.faces.config.InitFacesContext.(InitFacesContext.java:102)
  at com.sun.faces.config.ConfigureListener.contextInitialized(ConfigureListener.java:156)
  ..

Die Analyse

Dieser Fehler verhindert dann auch den Start der Anwendung. Woran liegt das? Dazu sehen wir uns mal eine Funktion der JSF-Referenz-Implementierung Mojarra an:

static Map getInitContextServletContextMap() {
  ConcurrentHashMap initContextServletContext = null;
  try {
    Field initContextMap = FacesContext.class.getDeclaredField("initContextServletContext");
    initContextMap.setAccessible(true);
    initContextServletContext = (ConcurrentHashMap)initContextMap.get(null);
  } catch (Exception e) {}
  return initContextServletContext;
}

Hierbei fällt auf, dass beim Zugriff mittels Reflection auf ein Feld der Fehlerfall einfach ignoriert wird und null zurück gegeben wird. Die Methode besitzt keine Dokumentation oder einen Hinweis darauf, was der Rückgabewert ist. Also muss der zugreifende Code den Fehler abfangen können. Aber tut er das? Sehen wir uns das mal an:

Map <InitFacesContext, ServletContext>initContextServletContext = InitFacesContext.getInitContextServletContextMap();
Set<Map.Entry<InitFacesContext, ServletContext>> entries = initContextServletContext.entrySet();

Damit wird schnell klar: Es wird NullPointerException geben und man sieht an der auftretenden Exception nicht den eigentlichen Grund. Dieses Fehlverhalten ist damit nicht ganz einfach zu beheben. Grundsätzlich ist es schlechter Stil eine Exception einfach zu schlucken. Besser ist es diese zumindest zu loggen, vielleicht sogar mit hilfreicher Zusatzinformation wie:

...
} catch (Exception e) {
  log.error("Could not access FacesContext#initContextServletContext, " +
    "probably JSF-API and implementation mismatch!", e);
}

Die Lösung

Jetzt ist offensichtlich, woher der Fehler kommt: die JSF-API und die Implementierung im Classpath passen nicht zusammen. In meinem Fall wurde eine weitere JSF-API über Maven als Abhängigkeit hinzugefügt, die dann „falsche“ Klassen in den Classpath lädt.

Es reicht also in den meisten Fällen den Classpath der Java-Anwendung auf folgende Probleme zu prüfen:

  • Passt die JSF-Implementierung zur JSF-API?
  • Gibt es mehr als eine JSF-Implementierung im Classpath (z.B. JSF 1.x und 2.x)?
  • Gibt es mehr als eine JSF-API im Classpath?

06.07.2012 - Hibernate Reverse Engineering

Hibernate kann mit seinen Reverse Engineering Tools sehr leicht ein Modell für eine existierende Datenbank erstellen. Aber was ist, wenn diese Datenbank nicht allen Regeln der NormalForm entspricht? Fangen wir zuerst an, wie eine hibernate.reveng.xml-Datei für MS SQL aussieht:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-reverse-engineering PUBLIC "-//Hibernate/Hibernate Reverse Engineering DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd" >
<hibernate-reverse-engineering>
  <schema-selection match-schema="dbo" />
</hibernate-reverse-engineering>

Dieser Code legt fest, dass wir immer mit dem dbo-Schema arbeiten wollen. Dies ist bei MS SQL das Standardschema. Nehmen wir jetzt an, dass wir die Tabelle ‚XYZ‘ ausschließen wollen, sprich es soll keine Mappingklasse für diese Tabelle erstellt werden:

<table-filter match-name="XYZ" exclude="true" />

Damit taucht diese Tabelle nicht mehr auf. Jetzt wollen wir die Spalte ‚abc‘ aus der Tabelle ‚ABC‘ ausschließen:

<table name="ABC" schema="dbo">
    <column name="abc" exclude="true" />
</table>

Und die Spalte ‚def‘ der Tabelle ‚ABC‘ hat den falschen Typ (es stehen Zahlen in einer VARCHAR-Spalte):

<column name="def" type="int" />

Und es fehlt auch noch ein Foreign Key von GHI auf DEF:

<table name="GHI" schema="dbo">
    <foreign-key foreign-table="DEF" foreign-schema="dbo">
        <column-ref local-column="defId" foreign-column="ID" />
    </foreign-key>
<table>

Und es gibt zwei Tabellen mit existierenden Foreign Keys, doch Hibernate generiert keinen brauchbaren Namen:

<table name="GHI" schema="dbo">
    <foreign-key constraint-name="FK_GHI_GFI">
        <column-ref local-column="defId" foreign-column="ID" />
        <many-to-one property="parent" />
        <set property="children"/>
    </foreign-key>
<table>

Und der Tabelle QWE fehlte der Primary Key:

<table name="QWE" schema="dbo"> 
    <primary-key> 
        <key-column name="ID" /> 
    </primary-key> 
</table>

All das simuliert der Modellgenerierung von Hibernate eine Datenbank, die vorhandene Schwächen versteckt, ohne tatsächlich Änderungen an der Datenkbank zu machen (und damit evtl. bestehende Anwendungen zu gefährden). Diese Beispiele veranschaulichen nur einen Teil dessen, wie man mit Hibernate ein brauchbares Datenmodell für eine ‚legacy‘ Datenbank erstellen kann. Ich kann als weitere Lektüre Chapter 6. Controlling reverse engineering empfehlen.

22.02.2012 - Is a DLL 32 or 64 bit?

Ich musste gerade auf einem Windowssystem herausfinden, ob eine DLL 32 oder 64 bit hat. Jeder, der den Befehl file unter Linux kennt, sucht nach einer ebenso einfachen Möglichkeit unter Windows. Wenn man nach diesem Problem online sucht, findet man diverse absurde Lösungen … ein Perl-Skript, kurz mal Visual Studio installiern, etc.

Die einfachste Lösung fand ich beim ReactOS-Projekt, den http://www.dependencywalker.com/. Dieser zeigt einfach bei allen Symbolen der DLL ob sie 64 oder 32 Bit verwenden. So einfach kann es sein und der Dependency Walker muss nicht mal installiert werden.

1 2 3 Weiter »
Copyright © christophbrill.de, 2002-2018.