g3blv
g3blv

Reputation: 4377

Passing Object type and Field to Comparator

Is it possible to write a Comparator so that I can pass the Object type, the field type and the field I like to sort on? I've made some small changes to http://www.davekoelle.com/files/AlphanumComparator.java to accommodate sorting on the field email of type String in the Object type User . I have this code that works.

public class Main {

    public static void main(String[] args) {

        List<User> users = new ArrayList<>();

        users.add(new User(7, "user1", "[email protected]"));
        users.add(new User(11, "user20", "[email protected]"));
        users.add(new User(5, "admin20", "[email protected]"));
        users.add(new User(10, "user11", "[email protected]"));
        users.add(new User(6, "admin21", "[email protected]"));
        users.add(new User(12, "user21", "[email protected]"));
        users.add(new User(8, "user2", "[email protected]"));
        users.add(new User(1, "admin1", "[email protected]"));
        users.add(new User(3, "admin10", "[email protected]"));
        users.add(new User(2, "admin2", "[email protected]"));
        users.add(new User(9, "user10", "[email protected]"));
        users.add(new User(4, "admin11", "[email protected]"));

        for (User item : users) {
            System.out.println(item.getEmail());
        }

        System.out.println("__________________________");

        Collections.sort(users, new AlphanumComparator());

        for (User item : users) {
            System.out.println(item.getEmail());
        }
    }
}

.

public class User {

    int id;
    String name;
    String email;

// Constructor, Getters and Setters
}

.

public class AlphanumComparator implements Comparator<User> {
    private final boolean isDigit(char ch) {
        return ((ch >= 48) && (ch <= 57));
    }

    /**
     * Length of string is passed in for improved efficiency (only need to calculate it once)
     **/
    private final String getChunk(String s, int slength, int marker) {
        StringBuilder chunk = new StringBuilder();
        char c = s.charAt(marker);
        chunk.append(c);
        marker++;
        if (isDigit(c)) {
            while (marker < slength) {
                c = s.charAt(marker);
                if (!isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        } else {
            while (marker < slength) {
                c = s.charAt(marker);
                if (isDigit(c))
                    break;
                chunk.append(c);
                marker++;
            }
        }
        return chunk.toString();
    }

    public int compare(User u1, User u2) {
        if ((u1 == null) || (u2 == null)) {
            return 0;
        }

        int thisMarker = 0;
        int thatMarker = 0;
        int s1Length = u1.getEmail().length();
        int s2Length = u2.getEmail().length();

        while (thisMarker < s1Length && thatMarker < s2Length) {
            String thisChunk = getChunk(u1.getEmail(), s1Length, thisMarker);
            thisMarker += thisChunk.length();

            String thatChunk = getChunk(u2.getEmail(), s2Length, thatMarker);
            thatMarker += thatChunk.length();

            // If both chunks contain numeric characters, sort them numerically
            int result = 0;
            if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
                // Simple chunk comparison by length.
                int thisChunkLength = thisChunk.length();
                result = thisChunkLength - thatChunk.length();
                // If equal, the first different number counts
                if (result == 0) {
                    for (int i = 0; i < thisChunkLength; i++) {
                        result = thisChunk.charAt(i) - thatChunk.charAt(i);
                        if (result != 0) {
                            return result;
                        }
                    }
                }
            } else {
                result = thisChunk.compareTo(thatChunk);
            }

            if (result != 0)
                return result;
        }

        return s1Length - s2Length;
    }
}

How can I pass Object type, field type and the field that I like to sort on from Collections.sort(users, new AlphanumComparator()); in the Main class to AlphanumComparator and how can I accommodate for this in AlphanumComparator? So in this case I would pass object type User field email and field type String. But if I like to sort on id I would pass object type User, field email and field type int.

Upvotes: 0

Views: 723

Answers (1)

Maurice Perry
Maurice Perry

Reputation: 9650

I would keep the AlphanumComparator as is, and create a new class FieldComparator:

public class FieldComparator<T> implements Comparator<T> {
    private static final Logger LOG = Logger.getLogger(
            FieldComparator.class.getName());
    private static final AlphanumComparator ALPHANUM = new AlphanumComparator();

    private final Field field;
    private final boolean isString;
    private final boolean isComparable;

    public FieldComparator(Class<T> clazz, String name) {
        try {
            field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            Class<?> fieldType = field.getType();
            isString = fieldType == String.class;
            isComparable = Comparable.class.isAssignableFrom(fieldType);
        } catch (NoSuchFieldException | SecurityException ex) {
            LOG.log(Level.SEVERE, null, ex);
            throw new RuntimeException(ex.getMessage());
        }
    }

    @Override
    public int compare(T o1, T o2) {
        try {
            Object value1 = field.get(o1);
            Object value2 = field.get(o2);
            if (value1 == null) {
                return value2 == null ? 0 : -1;
            } else if (value2 == null) {
                return 1;
            } else if (isString) {
                return ALPHANUM.compare((String)value1, (String)value2);
            } else if (isComparable) {
                return ((Comparable)value1).compareTo(value2);
            } else {
                // don't know how to compare fields
                return 0;
            }
        } catch (IllegalArgumentException | IllegalAccessException ex) {
            LOG.log(Level.SEVERE, null, ex);
            throw new RuntimeException(ex.getMessage());
        }
    }
}

UPDATE:

To deal with primitive types, you can change one line of method compare:

    } else if (isComparable || value1 instanceof Comparable) {

UPDATE 2:

You main method would become:

public static void main(String[] args) {

    List<User> users = new ArrayList<>();

    users.add(new User(7, "user1", "[email protected]"));
    users.add(new User(11, "user20", "[email protected]"));
    users.add(new User(5, "admin20", "[email protected]"));
    users.add(new User(10, "user11", "[email protected]"));
    users.add(new User(6, "admin21", "[email protected]"));
    users.add(new User(12, "user21", "[email protected]"));
    users.add(new User(8, "user2", "[email protected]"));
    users.add(new User(1, "admin1", "[email protected]"));
    users.add(new User(3, "admin10", "[email protected]"));
    users.add(new User(2, "admin2", "[email protected]"));
    users.add(new User(9, "user10", "[email protected]"));
    users.add(new User(4, "admin11", "[email protected]"));

    for (User item : users) {
        System.out.println(item.getEmail());
    }

    System.out.println("__________________________");

    Collections.sort(users, new FieldComparator(User.class, "email"));

    for (User item : users) {
        System.out.println(item.getEmail());
    }

    System.out.println("__________________________");

    Collections.sort(users, new FieldComparator(User.class, "name"));

    for (User item : users) {
        System.out.println(item.getEmail());
    }

    System.out.println("__________________________");

    Collections.sort(users, new FieldComparator(User.class, "id"));

    for (User item : users) {
        System.out.println(item.getEmail());
    }
}

Upvotes: 1

Related Questions