May 24, 2012

Apex Summer’12 Comparable interface Sorting Examples : Part 2 (Multi Attribute/Order Sorting)

In previous post i.e. “Apex Summer’12 Comparable interface Sorting Examples : Part 1”, we saw how easily we can sort a UDT(User Defined Type) on various attributes like Date, Datetime, String and Numbers.

This post will demo how we can get the ability to sort on

  • Multiple attributes
  • Both ASC and DESC sorting orders

All using the single Comparable interface. I wish Apex had “Comparators” like java, for more details on this gap, please check the “Summer’12 sorting review enhancements review” post.

Sample Apex Class – Employee

The same Employee class used as example in previous post will be enhanced here. Following is the initial code

global class Employee implements Comparable {
 public String name;
 public Integer age;
 public Date doj;
 public Datetime checkInTime;
  
 public Employee (String n, Integer a, Date d, DateTime c) {
  this.name = n;
  this.age = a;
  this.doj = d; 
  this.checkInTime = c;
 } 
 global Integer compareTo(Object other) {
   // will be shown in code snippets below
 }
}

Magic of “static”

static context i.e. variables are not like usual static in other languages like Java. In Java a static variable retains the value till the JVM(Java Virtual Machine) or application is running. But in Apex its having a totally different meaning because of multi-tenant nature of force.com platform, they can’t keep your statics available forever. If you are curious about how static works in Apex, I recommend reading this post first

Deep Dive - Static Context in Visualforce Apex Controllers !

The same special nature of static is used to support multi-attribute/order sorting via Comparable in this post.

Enhanced Employee Class

Here is the class code, I tried adding decent comments to make it more human readable and understandable Smile 

global class Employee implements Comparable {
  
 // Which field should be considered for sorting
 public enum SortField {
  Name, Age, DOJ, CheckInTime
 }
 
 // Sorting direction ASCENDING or DESCENDING
 public enum SortDirection {
  ASCENDING, DESCENDING
 }
 
 // default sorting would be ascending
 public static SortDirection SORT_DIR = SortDirection.ASCENDING;
 // default sorting would be on name
 public static SortField SORT_FIELD = SortField.Name;
 
 
 public String name;
 public Integer age;
 public Date doj;
 public Datetime checkInTime;
 
 public Employee (String n, Integer a, Date d, DateTime c) {
  this.name = n;
  this.age = a;
  this.doj = d; 
  this.checkInTime = c;
 }
 
 /*
  Comparable.compareTo() implementation 
 */
 global Integer compareTo(Object other) {
  if (SORT_FIELD == SortField.Name) {
     return compareToName(other); 
  } else if (SORT_FIELD == SortField.Age) {
     return compareToAge(other); 
  } else if (SORT_FIELD == SortField.DOJ) {
     return compareToDoj(other); 
  } else if (SORT_FIELD == SortField.CheckInTime) {
     return compareToCheckInTime(other); 
  }
  // this shouldn't be the case, add your error handling 
  // here as required
  return 0;
 }
 
 // compares CheckInTimeField
 Integer compareToCheckInTime(Object other) {
  // assuming if their is no datetime, it would be NOW
  // this might not be applicable for your biz logic
  // so please take care of that
  DateTime otherCheckInTime = other != null ? ((Employee)other).checkInTime : System.now();   
  // use Datetime.getTime() to do get the numeric time in millis
  if (SORT_DIR == SortDirection.ASCENDING)
   return (this.checkInTime.getTime() - otherCheckInTime.getTime()).intValue();
  else
   return (otherCheckInTime.getTime() - this.checkInTime.getTime()).intValue();
 }

 // Compares DOJ field
 Integer compareToDoj(Object other) {
  // assuming if their is no date, it would be TODAY
  // this might not be applicable for your biz logic
  // so please take care of that
  Date otherDOJ = other != null ? ((Employee)other).doj : System.today();   
  if (SORT_DIR == SortDirection.ASCENDING)  
   return otherDOJ.daysBetween(this.doj);
  else 
   return this.doj.daysBetween(otherDOJ);   
 }

 // Compares NAME field
 Integer compareToName(Object other) {
  String otherName = other != null ? ((Employee)other).name : '';   
  if (SORT_DIR == SortDirection.ASCENDING)     
   return this.name.compareTo(otherName);   
  else 
   return otherName.compareTo(this.name);
 }

 // Compares AGE field
 Integer compareToAge(Object other) {
  Integer otherAge = other != null ? ((Employee)other).age : 0;   
  if (SORT_DIR == SortDirection.ASCENDING)    
   return this.age - otherAge;
  else 
   return otherAge - this.age;   
 }
}

 

Testing the sorting

Execute this code snippet in anonymous block, and watch debug logs to trap the results. I have added how to sort on Name and Age fields in ASC and DESC order, you can sort of remaining two in similar fashion.

Employee [] employees = new Employee[] {
      new Employee('Abhinav', 30, Date.newInstance(2011, 06, 16), System.now().addHours(1)),
      new Employee('Vijay', 20, Date.newInstance(2010, 01, 11), System.now().addHours(2)),
      new Employee('John', 50, Date.newInstance(2007, 01, 1), System.now().addHours(3)), 
      new Employee('Bill', 40, Date.newInstance(2000, 04, 3), System.now().addHours(4))            
       };
// sort by default name and asc order
employees.sort();  
System.debug(employees);

// Reverse the sorting order to be desc
Employee.SORT_DIR = Employee.SortDirection.DESCENDING;
employees.sort();  
System.debug(employees);

// sort order on Age in ASC order
Employee.SORT_DIR = Employee.SortDirection.ASCENDING;
Employee.SORT_FIELD = Employee.SortField.Age;
employees.sort();  
System.debug(employees);

 

Related reading

Your Thoughts

Looking forward for the same.