Blog-Archiv

Sonntag, 13. März 2022

Generic Java Template Driven Comparator

Sometimes we need to sort a list of menu-items after a list of label-strings. That means, the template list contains elements of another data-type than the list to be sorted after the template list.

Example

Here is a simplified MenuItem class:

public class MenuItem
{
    public final String label;
    
    public MenuItem(String label) {
        this.label = Objects.requireNonNull(label);
    }

    @Override
    public String toString() {
        return label;
    }
}

And here is a template list that contains String objects instead of MenuItem:

    public static final String CUT = "Cut";
    public static final String COPY = "Copy";
    public static final String PASTE = "Paste";

    final List<Object> sorted = List.of(
        CUT, 
        COPY, 
        PASTE);

Can we sort a list of MenuItem after that list of String?

Generic Comparator

Yes we can ;-)
We need a generic extension of TemplateDrivenComparator (see my latest article for that class):

/**
 * Serves to sort lists where the template-list
 * has a different type than the list to sort.
 */
public abstract class GenericTemplateDrivenComparator extends TemplateDrivenComparator<Object>
{
    @Override
    public int compare(Object o1, Object o2) {
        return super.compare(convert(o1), convert(o2));
    }

    protected abstract Object convert(Object object);
}

This comparator uses the generic <Object> as data-type for list elements. It then overrides the compare() responsibility to convert each of the given parameters to a possibly different object before calling super.compare(). That way any MenuItem parameter can be converted to a String, which is necessary for super.compare() to work correctly.

Here is an example application:

 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
34
35
public class Main
{
    public static final String CUT = "Cut";
    public static final String COPY = "Copy";
    public static final String PASTE = "Paste";

    public static void main(String[] args) {
        final List<MenuItem> sequence = new ArrayList<>();
        sequence.add(new MenuItem(COPY));
        sequence.add(new MenuItem(PASTE));
        sequence.add(new MenuItem(CUT));
        System.out.println(sequence);
        
        final Comparator<Object> comparator = new GenericTemplateDrivenComparator()
        {
            private final List<Object> sorted =
                    List.of(
                        CUT, 
                        COPY, 
                        PASTE);
                
            @Override
            protected List<Object> template() {
                return sorted;
            }
            @Override
            protected Object convert(Object object) {
                return (object instanceof MenuItem) ? ((MenuItem) object).label : object;
            }
        };
        
        Collections.sort(sequence, comparator);
        System.out.println(sequence);
    }
}

Lines 3 - 5 define the labels. The main() procedure on line 7 builds a sequence of menu-items from that labels, which is to be sorted. It prints out the menu-item list on line 12.

Lines 14 - 30 define an anonymous comparator class that is made up to sort menu-items after a template list of strings. We see the template list on line 16 - 20, returned on line 24.

The convert() function on ine 27 - 29 uses the instanceof operator to find out of which data-type the given parameter is. In case it is a MenuItem, it returns its label, else it returns the template String directly.

Line 32 finally sorts the list of menu-items (physically).
Output of the application is:

[Copy, Paste, Cut]
[Cut, Copy, Paste]

Resume

Unfortunately I did not find a way how to implement this without using the instanceof operator. The Visitor pattern should be helpful in such cases, but ...




Keine Kommentare: