RegisterSearchFAQ UsergroupsLog in
Custom comparator, nullable last

 
Reply to topic    Citra Technologies Forum Index » Report a Bug View previous topic
View next topic

Custom comparator, nullable last
Author Message
sirokymartin



Joined: 28 Feb 2013
Posts: 9

Post Custom comparator, nullable last Reply with quote
I have a problem with custom comparator, when there are nulls values in the column. SortTableModel puts nulls values first and olny for not nulls calls the comparator. In the sorce code of SortTableModel I can see:
Code:

if (localObject1 == null) {
    i = localObject2 == null ? 0 : -1;
} else if (localObject2 == null) {
    i = 1;
} else {
    Comparator localComparator = locald.a;
    i = localComparator.compare(localObject1, localObject2);
}

so I can't define custom comparator to sort nulls values to the end of column in ascending order. I think all values (nulls and not nulls one) should be processed directly in comparator and not outside them.
Thank you,
Martin
Fri Mar 01, 2013 7:04 am View user's profile Send private message
support



Joined: 21 Feb 2006
Posts: 1463

Post Reply with quote
Hi,

Yes, you are right, SortTableModel works exactly as you described. The reason for this was to avoid having to perform null checking within every assigned comparator. Probably it would be best to code it as you say, but now we cannot change that, since there would be a problem with custom comparators used by previous customers that do not check for null values, in which case NullPointerExceptions will occur. You could create a SortTableModel subclass and override the compareRows method and do the comparison yourself, but I do not think that this is appropriate, since it requires some work. So I think that the best (and only) thing to do is for us to include a class variable that will determine the position of null values, to the beginning or end. Will you be ok with that?

Thanks
Fri Mar 01, 2013 8:10 am View user's profile Send private message
sirokymartin



Joined: 28 Feb 2013
Posts: 9

Post Reply with quote
Thank you, it will be fine.
Fri Mar 01, 2013 8:27 am View user's profile Send private message
support



Joined: 21 Feb 2006
Posts: 1463

Post Reply with quote
Hi there,

We made this addition in the latest version, 4.1.9. Null values are sorted at the beginning by default. You can change this with the setNullsPositionToStart method.

Thanks
Wed Mar 06, 2013 9:07 am View user's profile Send private message
sirokymartin



Joined: 28 Feb 2013
Posts: 9

Post Reply with quote
I have bought updated library, but I see, that this method is global for whole SortTableModel, so I can change nulls sorting behaviour for whole table.
I have object with two properties: "valid from" and "valid to"
For column "valid from" I need the nulls values first (record is valid from infinity) and then sorted notnulls dates
For column "valid to" I need sorted notnulls dates and then nulls last (record is valid to infinity)

The simplest solution will be to add new property on SortedTableModel that disables your nulls processing and so enables nulls processing in custom comparator:

Code:

if (!enableCustomNullsProcessing && localObject1 == null) {
   i = localObject2 == null ? 0 : j;
} else if (!enableCustomNullsProcessing && localObject2 == null) {
   i = -j;
} else {
   Comparator localComparator = localc.mm;
   i = localComparator.compare(localObject1, localObject2);
}


where enableCustomNullsProcessing = false by default
Fri Jul 26, 2013 7:41 am View user's profile Send private message
support



Joined: 21 Feb 2006
Posts: 1463

Post Reply with quote
Hi,

In version 4.1.16, you can now use different null-value processing independently. This is turned off by default.
To enable it, you should disable null processing in SortTableModel and then assign different comparators from the com.citra.comparators package to each column.

e.g.

Code:
sortTableModel.setEnableNullProcessing(false);

DateComparator nullFirst = new DateComparator();
sortTableModel.setComparator(0, nullFirst);

DateComparator nullLast = new DateComparator();
nullLast.setNullsPositionToStart(false);
sortTableModel.setComparator(1, nullLast);

Wed Aug 21, 2013 12:54 pm View user's profile Send private message
sirokymartin



Joined: 28 Feb 2013
Posts: 9

Post Reply with quote
Thank you, now it is working as I need.
Fri Aug 23, 2013 1:29 am View user's profile Send private message
navTeam2



Joined: 14 Jan 2011
Posts: 97

Post Reply with quote
Hi,

I come here for some advice:

I'm asked (don't ask why) to have one special column that should behave differently from the others.
Whatever the sort state (ascending or descending), null values should always be last.

Here is what I did, assuming my special column is index 0:

Code:

      StringComparator delegated = new StringComparator();

      BaseComparator bc = new BaseComparator() {

         @Override
         public int doCompare(Object o1, Object o2) {
            return delegated.compare(o1, o2);
         }

      };

      SortTableModel stm = new SortTableModel(ltm) {

         @Override
         public void setSortStates(SortState[] states) {
            for (SortState st : states) {
               if (st.getField() == 0) {
                  bc.setNullsPositionToStart(!st.isAscending());
               }
            }
            super.setSortStates(states);
         }
      };

      stm.setEnableNullProcessing(false);
      stm.setComparator(0, bc);


It works but I have a few questions:

What are the consequences of disabling NullProcessing ? Should I expect NullPointerException to occur because of null.compareTo(other) ? The javadoc isn't clear enough, I can't foresee what's that for. I'm worry something wrong happens to the other columns...

I'm not quite happy with my solution as I've inherited SortTableModel to do a little preprocessing before the sorting occurs.
I guess I can make it (a little) cleaner if I allow to set up an optional preprocessing:
MySortTableModel.setPreprocessing(Preprocessing p)
But it's still inheritance, I would prefer configuration or a listener notified before the sorting occur.

If i am to extend to random columns, with custom Comparator, here is what I come to:
Code:

      class MySortTableModel extends SortTableModel {

         private Consumer<SortState[]> preprocessing = null;

         private MySortTableModel(ListTableModel tableModel) {
            super(tableModel);
         }

         @Override
         public void setSortStates(SortState[] states) {
            if (preprocessing != null) {
               preprocessing.accept(states);
            }
            super.setSortStates(states);
         }

         public void setPreprocessing(Consumer<SortState[]> preprocessing) {
            this.preprocessing = preprocessing;
         }

      }

      class NullAlwaysLastHandler {

         private final MySortTableModel mstm;

         private final Map<Integer, BaseComparator> mapComparators = new HashMap<Integer, BaseComparator>();

         public NullAlwaysLastHandler(MySortTableModel mstm) {
            this.mstm = mstm;
            this.mstm.setEnableNullProcessing(false);
            this.mstm.setPreprocessing(new Consumer<SortState[]>() {

               @Override
               public void accept(SortState[] states) {
                  for (SortState s : states) {
                     BaseComparator baseComparator = mapComparators.get(s.getField());
                     if (baseComparator != null) {
                        baseComparator.setNullsPositionToStart(!s.isAscending());
                     }
                  }
               }
            });
         }

         public void setupComparatorNullAlwaysLast(int columnIndex, Comparator c) {
            BaseComparator baseComparator;
            if (c instanceof BaseComparator) {
               baseComparator = (BaseComparator) c;
            } else {
               baseComparator = new BaseComparator() {

                  @Override
                  public int doCompare(Object o1, Object o2) {
                     return c.compare(o1, o2);
                  }
               };
            }

            mapComparators.put(columnIndex, baseComparator);
            mstm.setComparator(columnIndex, baseComparator);
         }
      }

      MySortTableModel stm = new MySortTableModel(ltm);
      NullAlwaysLastHandler nullAlwaysLastHandler = new NullAlwaysLastHandler(stm);
      nullAlwaysLastHandler.setupComparatorNullAlwaysLast(0, new StringComparator());


MySortTableModel is left quite unchanged.
NullAlwaysLastHandler handles delegation and registration (poorly cause one can still replace the comparator setup without notifing the handler).

Thanks for any explanation or advice.

NavTeam
Thu Feb 09, 2017 4:19 am View user's profile Send private message
support



Joined: 21 Feb 2006
Posts: 1463

Post Reply with quote
Hi,

If you disable nullprocessing in SortTableModel, then the comparison for null values passes on to the comparators. All of the default comparators that SortTableModel uses, check for nulls, therefore a NullPointerException is never thrown. However, in that case, if you use your own comparator, you must ensure that nulls are checked in order to avoid NullPointerExceptions.

We thought of having a listener before and/or after sorting occurs, but this was never implemented, freedom to developers to do this is given by overriding the public method sortData(). I think I can put to the next release.

Now, if you want to have one or more column's sorting behaviour altered as all null values go to the end, you can do this without inheritance, just by assigning your special comparator to these columns:

Code:
StringComparator s = new StringComparator() {
   public int compare(Object o1, Object o2) {

      SortState[] ss = sortTableModel.getSortStates();
      for (int i=0;i<ss.length;i++) {
         if (ss[i].getField() == 0) {
            setNullsPositionToStart(!ss[i].isAscending());
            break;
         }
      }

      return super.compare(o1, o2);
   }
};
stm.setComparator(0, s);
stm.setEnableNullProcessing(false);


Which is the same as your solution, with the difference that setNullsPositionToStart is called before every comparison (overhead), instead of just once before sorting begins.
Tue Feb 14, 2017 7:07 am View user's profile Send private message
support



Joined: 21 Feb 2006
Posts: 1463

Post Reply with quote
Hi,

I added a listener for tracking the behaviour of SortTableModel - SorterEvent and SorterListener (see javadoc).

So, what you want is:

Code:
final StringComparator sc = new StringComparator();

sortTableModel.addSorterListener(new SorterListener() {
   public void sorterChanged(SorterEvent e) {
      if (e.getType() == SorterEvent.SORTING) {
         List<SortState> states = e.getSortStates();
         for (SortState st : states) {
            if (st.getField() == 0) {
               sc.setNullsPositionToStart(!st.isAscending());
               break;
            }
         }
      }
   }
});

sortTableModel.setComparator(0, sc);
sortTableModel.setEnableNullProcessing(false);


Thanks!
Tue Feb 14, 2017 3:13 pm View user's profile Send private message
navTeam2



Joined: 14 Jan 2011
Posts: 97

Post Reply with quote
Thanks for your explanations and for providing a listener way of doing it!

Thanks!

NavTeam
Wed Feb 15, 2017 3:22 am View user's profile Send private message
Display posts from previous:    

Reply to topic    Citra Technologies Forum Index » Report a Bug All times are GMT - 5 Hours
Page 1 of 1

 
Jump to: 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You can attach files in this forum
You can download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group