Archive for the ‘Java’ Category

MultiComparator

März 31st, 2009 2 Comments

Es ist mir bisher zwar erst zwei Mal untergekommen, aber das ist mir diesen Eintrag Wert.

Listen in Java zu sortieren ist dank vieler Vergleichsmethoden relativ problemlos möglich. Aber es soll auch vorkommen (wie bei mir jetzt das zweite Mal), dass man in der Liste nach mehr als einem Kriterium sortieren möchte. Hier kommt der MultiComparator zum Einsatz.

Den unrsprünglichen MultiComparator habe ich bei AllExperts.com gefunden. Diesen habe ich mal auf die neue Generics-Welt in Java umgeschrieben.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package what.ever.you.like;
 
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
 
/**
 * Comparator, der für das gegebene Objekt <code>T</code> mehrere Comperatoren
 * für eine Mehrfachsortierung anwenden kann.
 * @param <T>
 */
public class MultiComparator<T> implements Comparator {
 
  List<Comparator<T>> comparators = new ArrayList<Comparator<T>>();
 
  public void addComparator(Comparator comp) {
    comparators.add(comp);
  }
 
  public void removeComparator(Comparator comp) {
    comparators.remove(comp);
  }
 
  public int compare(T obj1, T obj2) {
    int result = 0;
    for (Comparator comp : comparators) {
        result = comp.compare(obj1, obj2);
        if (result != 0)
          break;
        }
      return result;
    }
  }

Diesem MultiComparator kann man jetzt weitere Comparatoren für ein beliebiges Objekt T hinzufügen. Diese sorgen dann für die eigentliche Sortierung. Hier ein kurzes Beispiel:

1
2
3
4
5
MultiComparator<SomeObject> multiComp = new MultiComparator<SomeObject>();
multiComp.add(new FirstOtherComparatorForSomeObject());
multiComp.add(new SecondOtherComparatorForSomeObject());
 
Collections.sort(listWithSomeObjects, multiComp);

Jetzt kann man für jeglichen complexeren Vergleich einen eigenen Comparator schreiben und diesen dann an den MultiComparator in der Reihenfolge anhängen, in welcher sortiert werden soll.

Das Problem mit Beispielen ist immer, daß es nur Beispiele sind und dadurch nicht alle Fälle abgedeckt werden, die im richtigen Einsatz nötig sind. In diesem Fall geht es darum, daß die Struts-Doku leider das Beispiel von Checkboxen gemeinsam mit Labels nicht im Repertoire hat.

Labels im Zusammenhang mit Checkboxen sind eine sehr hilfreiche Sache und tragen auch zur Usability bei.

Struts bietet mit html:multibox die Möglichkeit, Checkboxen zu gruppieren. Ein Aufruf aus dem JSP mit einem Iterator über eine gewisse Anzahl von Checkboxen sieht bspw. so aus:

1
2
3
4
5
6
7
8
<logic:iterate id="currentField" name="fields">
  <html:multibox
          property="fields"
          styleClass="checkbox">
    <bean:write name="currentField" property="id" />
  </html:multibox>
  <bean:write name="currentField" property="name" /><br />
</logic:iterate>

In Jim’s Blog habe ich ein gutes Beispiel gefunden, wie hier Labels ohne größeren Aufwand verwendet werden können:

1
2
3
4
5
6
7
8
9
10
11
12
13
<logic:iterate
          id="currentField"
          name="fields"
          indexId=”count”>
  <label for=”field.<bean:write name=’count’/>”>
  <html:multibox
          property=”fields”
          styleClass=”checkbox” 
          styleId=’<%=”field.”+count%>’>
    <bean:write name=”currentField” property=”id” />
  </html:multibox>
  <bean:write name=”currentField” property=”name” /><br />
</logic:iterate>

Im Quelltext sieht das ganze dann so aus:

1
2
3
4
5
6
7
8
<label for=”field.0″>
  <input type=”checkbox”
         name=”fields”
         value=”1″
         id=”field.0″
         class=”checkbox” />
  Ein Labelname<br />
</label>

Und schon hat man wieder mehr Usability erreicht.

Über die Criteria API von Hibernate lassen sich mittlerweile fast alle SQL-Queries wurderbar zusammenstellen. Nur hier und da gibt es ein paar Dinge, die schöner umgesetzt werden könnten. 

Ich habe bspw. eine Criteria auf ein Entity welches wiederum verknüpfte Entities hat. Meine Sortierung möchte ich auf ein Property des verknüpften Entities legen. Von der Crietria API sind wir es eigentlich gewohnt, so vorzugehen:

1
2
Criteria criteria = getCurrentSession().createCriteria(User.class);
criteria.addOrder(Order.asc("usergroup.name"));

Leider funktioniert dieser Code nicht, die Exception ist, das das Property “usergroup.name” nicht bekannt ist.
Um das Problem zu umgehen, muss man der Criteria explizit angeben, dass man auf das Entity “UserGroup” zugreifen möchte. Folgendes funktioniert demnach:

1
2
3
4
Criteria criteria = getCurrentSession().createCriteria(User.class);
Criteria subCrit = criteria.createCriteria(UserGroup.class);
// Sortierung auf UserGroup.name
subCrit.addOrder(Order.asc("name"));

Dieser “Bug”, das eine solche Sortierung überhaupt noch nicht unterstützt wurde, gilt nur für die Version 2.1. Schade ist, dass weiterhin eine Subcriteria verwendet werden muss, um die Sortierung dann auf die darunterliegende Property ausführen zu können. Der Weg über die Punkt-Notation wäre wohl schöner und leichter. 
Hoffentlich kommt das noch.

Ein Problem bei Struts ist, dass man nicht mehrere Formulare auf einer Seite haben kann, da das ActionMapping immer genau eine FormBean konfigurieren kann. Benötigt man jedoch eine solche Konstellation, muss man sich mit anderen Tricks helfen.

In meinem Fall habe ich ein Formular verwendet und dieses mit einem hidden-field ausgestattet. Per JavaScript wird bei verschiedenen Buttons zunächst das hidden-field gesetzt, und dann die Form abgeschickt. In der Action werden dann nur die entsprechenden Felder ausgewertet, die unter die Kategorie des Flags im hidden-field fallen. Soweit so gut, aber was ist mit Validierung?

Bei meiner Konstellation muss die Validierung über die validate()-Methode im FormBean geschehen, da ja je nach gesetztem Flag nicht alle Felder benötigt und somit nicht validiert werden. Die validation.xml kann in diesem Fall leider nicht verwendet werden, da diese immer die komplette FormBean überprüft, ungeachtet der Tatsache, dass ja nur ein Teil der FormBean-Daten zur Weiterverarbeitung verwendet werden soll.

Da Struts die Apache Commons Validations Bibliothek implementiert, kann dieses Framework auch gut vom Programmcode aus genutzt werden. Mein Problem war bspw. die Überprüfung eines Datums mit der richtigen Schreibweise. Hierfür gibt es schon eine fertige DateValidation.

Meine validate()-Methode sieht dann so aus:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public ActionErrors validate(ActionMapping mapping, HttpServletRequest request) {
  ActionErrors errors = super.validate(mapping, request);
  SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy", Locale.GERMAN);
 
  // überprüfen, welcher Formularteil ausgewertet werden soll
  if (this.hiddenFieldFlag.equals("firstForm")) {
    // Überprüfung des Datumsformates
    boolean checkDate = DateValidator.getInstance().isValid(date, sdf.toPattern(), true);
    /*
     * ... weitere Validierungen und ggf. ActionErrors setzen ...
     */
  }
  if (this.hiddenFieldFlag.equals("secondForm")) {
    // ...
  }
  return errors;
}

Einen anderen Weg, auf die vorkonfigurierten Validierungen der XML-Datei zuzugreifen, habe ich bisher leider nicht gefunden. Sollte jemand hierfür eine Lösung haben, wäre ich dankbar für die Information.