
preface
Sorting can be easily realized by using Collator class, but we may need to sort all kinds of models, so there will be a problem. If we write a sorting code for each model separately, the code will be very repetitive.
So I'm going to write a general tool to solve it by using generic + annotation + reflection.
annotation
First, create an annotation class
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.METHOD}) @Documented public @interface SortString { }
The purpose of this annotation is to identify which field or function is used for sorting.
Tool class
Then there is the sorting tool class. In my tool, the sorting rules are: Chinese > number > English. This is the requirement 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 { return ComparisonChain.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); } }
This class is large and comprehensive. It provides many methods to sort, including four main functions:
- sortByFieldName: sort by the field corresponding to the given field name
- sortByFieldAnnotation: sort by field with @ SortString
- sortByMethodName: sort by the function corresponding to the given function name
- sortByMethodAnnotation: sort by function with @ SortString
Which note
- sortByMethodName to ensure that there is no function with the same name in this class, of course, you can also add verification function parameter signature to prevent functions with the same name.
- If you sort by annotation, if there are multiple @ SortString annotations in the class, only the first one is effective. At present, this tool is only for single dimension sorting. Of course, it can be modified to multi-dimensional sorting, and a priority can be added. However, our requirements are not so complex, so we did not develop them.
use
It's easy to use. Just annotate the field (or function) used for sorting with @ SortString annotation in the class, such as
public class Experience { @SortString public String name; public int publicTime; public String summy; ... }
Then you can use simplified Chinese sorter Sort the Experience list by sortbyfieldannotation.
Of course, you can also use simplified Chinese sorter sortByFieldName, however, is relatively easy to use and maintain. Functions such as sortByFieldName are mainly used for classes that we do not have permission to modify (such as classes in third-party libraries). In this way, there is no way to add comments. It is also convenient to sort through functions such as sortByFieldName.