Monday, September 21, 2015

Sorting a Java ArrayList of SelectItem Objects

Recently I had a task where I needed to read all the names from several groups in the Name & Address Book.  I needed to get all the names sorted and unique for presenting in a Combobox.

In this post, I will show you how I did this, but more importantly I will show how I used the Java Collections Framework to my advantage.

In case you aren’t aware, the SelectItem object is the object type that Java Server Faces (thereby XPages) uses for the values of a Combo Box. It contains a value and a label, of which the label is what is shown and the value is what is saved.

Because the SelectItem does not implement the Comparator interface, you cannot easily sort an ArrayList of SelectItems. You could write your own sorting method if you like using the instructions outlined in this blog post. I considered this but then thought of an easier way to accomplish the same thing.

Here is what I did to solve this: Instead of creating just an ArrayList of SelectItems, I also created one of Strings. Strings of course are easily sortable since the String class does implement Comparator interface. Prior to sorting the list, I need to removed duplicates. To do this, I first create a HashSet and assign the values to it. A Set in java does not allow duplicates, so assigning all the values to the HashSet was the easiest way to remove the dupes. After this is done, I assign all the values back into the ArrayList and then sort the list. I then create an SelectItem ArrayList with the unique sorted values.

Note: This would be a lot more complicated if I needed my SelectItem object to have different Strings for Label and Value. I would probably use a HashMap instead of an ArrayList<String> if this was the case.

Code Example


public List<SelectItemgetUserList(String schema){ 
1  List<String> options = new ArrayList<String>(); 
List<SelectItemsortedOptions = new ArrayList<SelectItem>();

try { 
  Session session = getCurrentSession(); 
  Database nab = session.getDatabase(session.getCurrentDatabase().getServer(), "names");

if (nab.isOpen()) { 
View view = nab.getView("Groups"); 
if (view != null) { 
Document doc = view.getDocumentByKey("Group 1"true); 
if (doc != null) { 
Item item = doc.getFirstItem("Members"); 
Vector<String> v = item.getValues(); 
for(String person : v){ 
Name name = session.createName(person);  options.add(name.getCommon()); 
} 
doc.recycle(); 
item.recycle(); 
else { 
log.error("Group 1 cannot be opened."); 
} 
doc = view.getDocumentByKey("Group 2"true); 
if (doc != null) { 
Item item = doc.getFirstItem("Members"); 
Vector<String> v = item.getValues(); 
for(String person : v){ 
Name name = session.createName(person); 
options.add(name.getCommon()); 
} 
doc.recycle(); 
item.recycle(); 
else { 
log.error("Group cannot be found, or opened."); 
} 
……………… //get names from third group 
} 
else { 
log.error("Address Book is not found, or able to be opened."); 
} 
catch (NotesException e) { 
log.error("EXCEPTION in getUserList(): " + e.toString()); 
e.printStackTrace(); 
} 

Set<String> hs = new HashSet<String>();
hs.addAll(options); 
options.clear(); 
options.addAll(hs); 

Collections.sort(options); 

for(String person : options){  
SelectItem option = new SelectItem(); 
option.setLabel(person); 
option.setValue(person); 
sortedOptions.add(option); 
} 
return sortedOptions; 
}

Code Explanation using footnotes:


1  - Declare two ArrayLists, one for Strings, and one for SelectItems.

2  - The values of a multi-value field are returned as a Vector. For each item in the Vector, I assign the item into the String ArrayList

3 - This uses the Name class to get the formatted name. This is part of the original Notes API for java, and is quite useful.

4 -  This is where I pass the contents of the String ArrayList into a HashSet and then back again. All Sets in Java are not able to contain duplicate values. 

5 - After the duplicates are removed, then I sort the list. If you try to sort before removing duplicates it won't be sorted after changing to a Hashset and back. As you can see, sorting a String List is very easy using the Collections class.

6 -  Lastly, I convert the ArrayList of Strings into an ArrayList of SelectItems for use in my combo box.

Last Words


In Java the Collections Framework is super useful and one of the strengths of the language. If you are learning Java it would wise to pay special attention to knowing what it can do for you.  

Please check comments of this post for some great tips on how to improve on what I posted.

7 comments:

  1. What a great tip, fits perfectly in my Java learning, many thanks for sharing.

    Mark

    ReplyDelete
  2. I only looked at the code quickly... but a couple thoughts...

    I'm not sure why you're initially using an ArrayList for options... this is what you're putting all the names into initially right? I'm missing the need for an ArrayList here...

    I'd think you'd want to use a TreeSet. TreeSet is just like a hashSet but it's sorted as you add things. So there's no need to then do an extra sort and conversion to and from an ArrayList.

    Populate the names into a TreeSet... then loop through that to make you're selectItems....

    Just my quick thoughts... I could of course be wrong.. :)

    ReplyDelete
  3. A cleaner approach would be to use a TreeSet, which will both filter out duplicates and put them in order as you insert them. You can provide the Comparator when you create the set.

    ReplyDelete
  4. And if you wanted different values for Label and Value... you could then use a TreeMap TreeMap does not allow duplicates...

    As you populate the treeMap the key is ... say person name... and the value portion could be the email address... Since it's a treeMap it'll auto sort on the key....

    myTreeMap.put(myName, myEmailAddress)

    Then in your loop of select Items you'd loop through the treeMap... something like this:

    for(Map.Entry entry : myTreeMap.entrySet()) {


    SelectItem option = new SelectItem();
    option.setLabel(entry.getKey());
    option.setValue(entry.getValue());
    sortedOptions.add(option);
    }

    Something like that....

    In theory at least. :)

    ReplyDelete
    Replies
    1. Thank you both Steve and David. I was able to use sorted tree map to build my combo box. Your pseudo code helped me David.

      Delete
  5. What a horrible piece of code. Your code has a lot of design flaws:

    -Using vector instead of List
    -A lot of if/for nested.
    -Function too large
    -Swallow exceptions (do you really want to continue if there is a NotesException????) (logging an error does not count as handling an error)

    ReplyDelete
  6. What a horrible piece of code. Your code has a lot of design flaws:

    -Using vector instead of List
    -A lot of if/for nested.
    -Function too large
    -Swallow exceptions (do you really want to continue if there is a NotesException????) (logging an error does not count as handling an error)

    ReplyDelete