“This is the 15th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

preface

Sorting can be done easily with the Collator class, but we may have a variety of models that need to be sorted, and the problem is that if we write a separate sorting code for each model, the code will be very repetitive.

So I decided to write a generic tool that uses generics + annotations + reflection.

annotations

First, create the annotation class

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Documented
public @interface SortString {
}
Copy the code

The purpose of this annotation is to identify which field or function is used for sorting.

Utility class

Then there is the sorting tool class. In my tool, the sorting rule is: Chinese > Number > English, which is the demand of our app. You can modify it according to your own needs.

The complete code is as follows

public class SimplifiedChineseSorter {
    private static final String SORTINGREGEX = "[^\\p{L}\\p{N}]+|^(The|A|An)\\b";
    private static final Collator stringComparator = Collator.getInstance(Locale.SIMPLIFIED_CHINESE);

    public static <T> List<T> sortByProvider(List<T> items, SortStringProvider<T> provider, boolean isIgnoreCase) {
        if (items == null || items.size() <= 0) {
            return null;
        }
        return sortList(items, provider, isIgnoreCase);
    }

    public static <T> List<T> sortByFieldName(List<T> items, String fieldName, boolean isIgnoreCase) {
        if (items == null || items.size() <= 0) {
            return null;
        }
        Field field = getSortStringField(items.get(0).getClass(), fieldName);
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(field);
        return sortList(items, provider, isIgnoreCase);
    }

    public static <T> List<T> sortByFieldAnnotation(List<T> items, boolean isIgnoreCase) {
        if (items == null || items.size() <= 0) {
            return null;
        }
        Field field = getSortStringField(items.get(0).getClass());
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(field);
        return sortList(items, provider, isIgnoreCase);
    }

    public static <T> List<T> sortByMethodName(List<T> items, String methodName, boolean isIgnoreCase) {
        if (items == null || items.size() <= 0) {
            return null;
        }
        Method method = getSortStringMethod(items.get(0).getClass(), methodName);
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(method);
        return sortList(items, provider, isIgnoreCase);
    }

    public static <T> List<T> sortByMethodAnnotation(List<T> items, boolean isIgnoreCase) {
        if (items == null || items.size() <= 0) {
            return null;
        }
        Method method = getSortStringMethod(items.get(0).getClass());
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(method);
        return sortList(items, provider, isIgnoreCase);
    }

    private static <T> List<T> sortList(List<T> items, final SortStringProvider<T> provider, final boolean isIgnoreCase) {
        if(provider == null) {return items;
        }
        final List<T> chinieseList = new ArrayList<T>();
        final List<T> nonChineseList = new ArrayList<T>();
        for (T item : items) {
            if (isChineseCharStart(format(provider.getSortString(item), isIgnoreCase))) {
                chinieseList.add(item);
            } else {
                nonChineseList.add(item);
            }
        }
        List<T> sortedChineseList = Ordering.from(new Comparator<T>() {
            @Override
            public int compare(T lhs, T rhs) {
                return stringComparator.compare(format(provider.getSortString(lhs), isIgnoreCase), format(provider.getSortString(rhs), isIgnoreCase));
            }
        }).sortedCopy(chinieseList);
        List<T> sortedNonChineseList = Ordering.from(new Comparator<T>() {
            @Override
            public int compare(T lhs, T rhs) {
                return format(provider.getSortString(lhs), isIgnoreCase).compareTo(format(provider.getSortString(rhs), isIgnoreCase));
            }
        }).sortedCopy(nonChineseList);
        sortedChineseList.addAll(sortedNonChineseList);
        return sortedChineseList;
    }

    public static <T> Comparator<T> getSortComparatorByProvider(final Class clazz, SortStringProvider<T> provider, boolean isIgnoreCase) {
        return getSortComparator(provider, isIgnoreCase);
    }

    public static <T> Comparator<T> getSortComparatorByFieldName(final Class clazz, final String fieldName, boolean isIgnoreCase) {
        Field field = getSortStringField(clazz, fieldName);
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(field);
        return getSortComparator(provider, isIgnoreCase);
    }

    public static <T> Comparator<T> getSortComparatorByFieldAnnotation(final Class clazz, boolean isIgnoreCase) {
        Field field = getSortStringField(clazz);
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(field);
        return getSortComparator(provider, isIgnoreCase);
    }

    public static <T> Comparator<T> getSortComparatorByMethodName(final Class clazz, final String methodName, boolean isIgnoreCase) {
        Method method = getSortStringMethod(clazz, methodName);
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(method);
        return getSortComparator(provider, isIgnoreCase);
    }

    public static <T> Comparator<T> getSortComparatorByMethodAnnotation(final Class clazz, boolean isIgnoreCase) {
        Method method = getSortStringMethod(clazz);
        DefualtSortStringProvider<T> provider = new DefualtSortStringProvider<T>(method);
        return getSortComparator(provider, isIgnoreCase);
    }

    private static <T> Comparator<T> getSortComparator(final SortStringProvider<T> provider, final boolean isIgnoreCase) {
        return new Comparator<T>() {
            @Override
            public int compare(final T left, final T right) {
                String leftStr = format(provider.getSortString(left), isIgnoreCase);
                String rightStr = format(provider.getSortString(right), isIgnoreCase);
                if (SimplifiedChineseSorter.isChineseCharStart(leftStr) &&
                        SimplifiedChineseSorter.isChineseCharStart(rightStr)) {
                    return stringComparator.compare(leftStr, rightStr);
                } else {
                    returnComparisonChain.start() .compareTrueFirst(SimplifiedChineseSorter.isChineseCharStart(leftStr), SimplifiedChineseSorter.isChineseCharStart(rightStr)) .compare(leftStr, rightStr, Ordering.natural().nullsFirst()) .result(); }}}; }public static boolean isChineseCharStart(String str) {
        return! str.matches("[A Za - z0-9 \" "] +. * "");
    }

    private static <T> Field getSortStringField(Class<T> tClass) {
        Field[] fields = tClass.getDeclaredFields();
        if(fields ! =null) {
            for (Field field : fields) {
                if (field.isAnnotationPresent(SortString.class) && field.getType() == String.class) {
                    field.setAccessible(true);
                    return field;
                }
            }
        }
        Class superClass = tClass.getSuperclass();
        if(superClass ! =null && !superClass.equals(Object.class)){
            return getSortStringField(superClass);
        }
        throw new RuntimeException("The model doesn't have a @SortString field or the type of @SortString field is not a String");
    }

    private static <T> Field getSortStringField(Class<T> tClass, String sortFieldName) {
        Field field = null;
        try {
            field = tClass.getDeclaredField(sortFieldName);
        } catch (NoSuchFieldException e) {
        }
        finally {
            if(field ! =null && field.getType() == String.class) {
                field.setAccessible(true);
                return field;
            }
            Class superClass = tClass.getSuperclass();
            if(superClass ! =null && !superClass.equals(Object.class)){
                return getSortStringField(superClass, sortFieldName);
            }
            throw new RuntimeException("The model doesn't have a field named "+ sortFieldName); }}private static <T> Method getSortStringMethod(Class<T> tClass) {
        Method[] methods = tClass.getDeclaredMethods();
        if(methods ! =null) {
            for (Method method : methods) {
                if (method.isAnnotationPresent(SortString.class) && method.getReturnType() == String.class) {
                    method.setAccessible(true);
                    return method;
                }
            }
        }
        Class superClass = tClass.getSuperclass();
        if(superClass ! =null && !superClass.equals(Object.class)){
            return getSortStringMethod(superClass);
        }
        throw new RuntimeException("The model doesn't have a @SortString method or the returnning type of @SortString method is not a String");
    }

    private static <T> Method getSortStringMethod(Class<T> tClass, String sortMethodName) {
        Method method = null;
        try {
            method = tClass.getDeclaredMethod(sortMethodName);
        } catch (NoSuchMethodException e) {
        }
        finally {
            if(method ! =null && method.getReturnType() == String.class) {
                method.setAccessible(true);
                return method;
            }
            Class superClass = tClass.getSuperclass();
            if(superClass ! =null && !superClass.equals(Object.class)){
                return getSortStringMethod(superClass, sortMethodName);
            }
            throw new RuntimeException("The model doesn't have a method named "+ sortMethodName); }}private static String format(String data, boolean isIgnoreCase) {
        Pattern pattern = Pattern.compile(SORTINGREGEX, Pattern.CASE_INSENSITIVE);
        if(data == null) {return "";
        }
        else if(isIgnoreCase){
            return pattern.matcher(data.trim()).replaceAll("").toLowerCase();
        }
        else{
            return pattern.matcher(data.trim()).replaceAll(""); }}static class DefualtSortStringProvider<T> implements SortStringProvider<T>{
        private Object orderBy;

        DefualtSortStringProvider(Object orderBy){
            this.orderBy = orderBy;
        }

        public String getSortString(T obj){
            try {
                if (orderBy instanceof Field) {
                    return (String) ((Field) orderBy).get(obj);
                } else if (orderBy instanceof Method) {
                    return(String) ((Method)orderBy).invoke(obj); }}catch (Exception e){
                Log.e("SimplifiedChineseSorter"."getSortString", e);
            }
            return ""; }}public interface SortStringProvider<T>{
        String getSortString(T obj); }}Copy the code

This class is quite large and comprehensive, and provides many methods for sorting, including four main functions:

  • SortByFieldName: Sort by the field corresponding to the given field name
  • SortByFieldAnnotation: Sort by field with @sortString
  • SortByMethodName: Sorts by the function corresponding to the given function name
  • SortByMethodAnnotation: sort by a function with @sortstring

The note

  1. SortByMethodName ensures that there are no functions with the same name in this class. You can also add validation function parameter signatures to prevent functions with the same name.
  2. If you sort by comment, if you have multiple @sortString annotations in your class, only the first one will have any effect. Currently, this tool only deals with single-dimensional sorting, but you can change it to multi-dimensional sorting by adding a priority. But our requirements weren’t that complex, so we didn’t develop it.

use

It’s as simple as annotating the field (or function) in the class with @sortString, for example

public class Experience {
    @SortString
    public String name;
    public int publicTime;
    publicString summy; . }Copy the code

Then you can use SimplifiedChineseSorter. SortByFieldAnnotation to Experience list is sorted.

. Of course you can also use SimplifiedChineseSorter sortByFieldName, but it is more convenient to use comments relatively easy to maintain, provide sortByFieldName such function, mainly used for some we don’t have permission to modify the class (for example, the three parties in the library class), Instead of adding comments, you can easily sort them using a function like sortByFieldName.