5
 @Entity
 @NamedQueries({
   @NamedQuery(
     name = "FolderNode.findByName",
     query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode = :parentNode"),
   @NamedQuery(
     name = "FolderNode.findRootNodeByName",
     query = "SELECT f FROM FolderNode f WHERE f.name = :name AND f.parentNode is null")
 })
 public class FolderNode extends InstructorTreeNode {
   public FolderNode() {
     super();
   }

   public FolderNode(String name) {
     this();
     setName(name);
   }

   public FolderNode(int sortOrder, String name) {
     this(name);
     this.sortOrder = sortOrder;
   }

   public FolderNode(int sortOrder, String name, EmployeeState status) {
     this(sortOrder, name);
     this.status = status;
   }

   public static FolderNode addWaitingListNode(String name) {
     EntityManager em = getDao().getEntityManager();
     em.getTransaction().begin();
     FolderNode waitingListNode = getWaitingListFolder();
     FolderNode folderNode = new FolderNode(0, name);
     waitingListNode.addChild(folderNode);
     em.merge(waitingListNode);
     em.getTransaction().commit();
     em.close();
     return folderNode;
   }

   public static void addWaitingListStudent(String waitingList, Student s) {
     EntityManager em = FolderNode.getDao().getEntityManager();
     em.getTransaction().begin();
     FolderNode waitingListsNode = getWaitingListFolder();
     FolderNode waitingListNode = getDao().findFolderNodeByName(waitingListsNode, waitingList);
     waitingListNode.addChild(new EmployeeLeaf(s.getInmate()));
     em.merge(waitingListNode);
     em.getTransaction().commit();
     em.close();
   }

   public static FolderNode getAMClassFolder() {
     return getDao().findFolderNodeByName(getStudentsFolder(), "AM Class");
   }

   public static FolderNode getAttendanceFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Employee Attendance");
   }

   public static FolderNode getFormerParaprosFolder() {
     return getDao().findFolderNodeByName(getParaprosFolder(), "Former");
   }

   public static FolderNode getFormerStudentsFolder() {
     return getDao().findFolderNodeByName(getStudentsFolder(), "Former");
   }

   public static FolderNode getPMClassFolder() {
     return getDao().findFolderNodeByName(getStudentsFolder(), "PM Class");
   }

   public static FolderNode getParaprosFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Parapros");
   }

   public static FolderNode getPendingStudentsFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Pending Students");
   }

   public static FolderNode getRootFolder() {
     return getDao().findFolderNodeByName(null, EducationPreferences.getInstructor().getInstructorName());
   }

   public static FolderNode getStudentsFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Students");
   }

   public static FolderNode getWaitingListFolder(String name) {
     FolderNode waitingListsNode = getWaitingListFolder();
     return getDao().findFolderNodeByName(waitingListsNode, name);
   }

   public static FolderNode getWaitingListFolder() {
     return getDao().findFolderNodeByName(getRootFolder(), "Waiting List");
   }

   public static void setClassFolder(Student aStudent, EntityManager entityManager) {
     EntityManager em = entityManager;
     if (entityManager == null) {
       em = FolderNode.getDao().getEntityManager();
       em.getTransaction().begin();
     }

     EmployeeLeaf leaf = EmployeeLeaf.findActiveStudentLeaf(aStudent);
     FolderNode node = aStudent.getShift() == Shift.AM ? getAMClassFolder() : getPMClassFolder();
     leaf.setParentNode(node);
     em.merge(leaf);
     GlobalEntityMethods.updateHistory(leaf);
     if (entityManager == null) {
       em.getTransaction().commit();
       em.close();
     }
   }

   public static void transferWaitingListStudent(String currentFolder, String toFolder, Student student) {
     EntityManager em = FolderNode.getDao().getEntityManager();
     em.getTransaction().begin();
     FolderNode waitingListsNode = getWaitingListFolder();
     FolderNode currentWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, currentFolder);
     EmployeeLeaf employeeLeaf = EmployeeLeaf.getDao().findWaitingListLeafByInmate(student.getInmate());
     currentWaitingListNode.removeChild(employeeLeaf);
     FolderNode toWaitingListNode = getDao().findFolderNodeByName(waitingListsNode, toFolder);
     toWaitingListNode.addChild(employeeLeaf);
     em.merge(currentWaitingListNode);
     em.merge(toWaitingListNode);
     em.getTransaction().commit();
     em.close();
   }

   public void addChild(InstructorTreeNode node) {
     childNodes.add(node);
     node.setParentNode(this);
   }

   public List<InstructorTreeNode> getChildNodes() {
     Collections.sort(childNodes);
     return childNodes;
   }

   @Override
   public Set<Inmate> getInmates() {
     Set<Inmate> inmateSet = new HashSet<> (50);
     for (InstructorTreeNode node: getChildNodes()) {
       inmateSet.addAll(node.getInmates());
     }
     return inmateSet;
   }

   public int getSortOrder() {
     return sortOrder;
   }

   public EmployeeState getStatus() {
     return status;
   }

   @Override
   public List<InstructorTreeNode> getTree() {
     List <InstructorTreeNode> result = new ArrayList<> (25);
     for (InstructorTreeNode childNode: getChildNodes()) {
       if (childNode instanceof FolderNode) {
         result.add(childNode);
       }
       result.addAll(childNode.getTree());
     }
     return result;
   }

   @Override
   public JPanel getView(EmployeeViewController controller) {
     if ("Employee Attendance".equals(getName())) {
       return new AttendanceView();
     } else if ("Waiting List".equals(getName())) {
       return new AllWaitingListsPanel(controller);
     } else if (getParentNode().getName().equals("Waiting List")) {
       return new WaitingListPanel(controller);
     } else if ("Pending Students".equals(getName())) {
       return new PendingStudentsPanel(controller);
     } else if ("Students".equals(getName())) {
       return new AllStudentsPanel(controller);
     } else if ("AM Class".equals(getName())) {
       return new AllStudentsPanel(controller, Shift.AM);
     } else if ("PM Class".equals(getName())) {
       return new AllStudentsPanel(controller, Shift.PM);
     } else if (getParentNode().getName().equals("Students") && "Former".equals(getName())) {
       return new FormerStudentsPanel(controller);
     } else if ("Parapros".equals(getName())) {
       return new AllParaprosPanel(controller);
     } else if (getParentNode().getName().equals("Parapros") && "Former".equals(getName())) {
       return new FormerParaprosPanel(controller);
     }
     throw new UnsupportedOperationException("unknown folder");
   }

   public void removeChild(InstructorTreeNode node) {
     childNodes.remove(node);
     node.setParentNode(null);
   }

   public void removeEmployeeLeaf(Inmate inmate) {
     for (InstructorTreeNode node: childNodes) {
       if (node instanceof EmployeeLeaf) {
         EmployeeLeaf employeeLeaf = (EmployeeLeaf) node;
         if (employeeLeaf.getInmate().equals(inmate)) {
           childNodes.remove(employeeLeaf);
           break;
         }
       }
     }
   }

   public void setChildNodes(List<InstructorTreeNode> childNodes) {
     this.childNodes = childNodes;
   }

   public void setSortOrder(int sortOrder) {
     this.sortOrder = sortOrder;
   }

   public void setStatus(EmployeeState status) {
     this.status = status;
   }

   @OneToMany(mappedBy = "parentNode", cascade = CascadeType.ALL, orphanRemoval = true)
   private List<InstructorTreeNode> childNodes;

   private int sortOrder;

   @Enumerated(EnumType.STRING)
   private EmployeeState status;
 }


 @Entity
 @Table(catalog = "education", name = "instructortreenode", uniqueConstraints = @UniqueConstraint(columnNames = {
   "PARENTNODE_ID", "NAME"
 }))
 @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
 public abstract class InstructorTreeNode implements Comparable<InstructorTreeNode> {
   public InstructorTreeNode() {
     super();
   }

   public static InstructorTreeNodeDAO getDao() {
     return dao;
   }

   @Override
   public int compareTo(InstructorTreeNode o) {
     if (o instanceof FolderNode && this instanceof FolderNode) {
       FolderNode thisFolder = (FolderNode) this;
       FolderNode otherFolder = (FolderNode) o;
       if (thisFolder.getSortOrder() != otherFolder.getSortOrder()) {
         return thisFolder.getSortOrder() - otherFolder.getSortOrder();
       } else {
         return thisFolder.getName().compareToIgnoreCase(otherFolder.getName());
       }
     } else if (o instanceof EmployeeLeaf && this instanceof EmployeeLeaf) {
       return getName().compareToIgnoreCase(((InstructorTreeNode) o).getName());
     }
     return (o instanceof FolderNode) ? -1 : +1;
   }

   public int getCount() {
     return getTree().size();
   }

   public abstract Set<Inmate> getInmates();

   public String getName() {
     return name;
   }

   public FolderNode getParentNode() {
     return parentNode;
   }

   public abstract List<InstructorTreeNode> getTree();

   public abstract JPanel getView(EmployeeViewController theController);

   public void setName(String name) {
     this.name = name;
   }

   public void setParentNode(FolderNode parentNode) {
     this.parentNode = parentNode;
   }

   @Override
   public String toString() {
     return name;
   }

   private static final InstructorTreeNodeDAO dao = new InstructorTreeNodeDAO();
   private String name;

   @ManyToOne
   private FolderNode parentNode;
 }

Here is my problem: The Collections.sort line works just fine in Java 8u5 and before, but in Java 8u20 they seem to have changed the code for Collections.sort and it no longer uses anything but the natural order, even if you specify a Comparator.

Should I be using another method to sort my list, or is there an error in Collections.sort.

Any help would be much appreciated, as this is driving me crazy.

I forgot to say that this code does not use a specified comparator, but according to the documentation it is supposed to use the CompareTo, if your class implements Comparable, which is what I am using. I tried also specifying a comparator, but it did not work either.

Richard Neish
  • 8,414
  • 4
  • 39
  • 69
D. Milenski
  • 63
  • 1
  • 4
  • But you're not specifying a different Comparator? What code gives the error, and what error message do you get? – Louis Wasserman Nov 20 '14 at 23:30
  • I am not getting an error message of any kind. My class InstructorTreeNode implements Comparable. According to the JavaDocs, if you do not implement Comparable you list will be sorted by the natural order, but IF you implement Comparable it is supposed to use the order specified in CompareTo to sort the list. This has been working fine, until upgrading to Java 8u20. – D. Milenski Nov 25 '14 at 15:57
  • 1
    That's...not how it works. If you don't implement `Comparable` there _is_ no natural order. You _must_ implement `Comparable` to use `Collections.sort(list)` with no `Comparator`, and that `Comparable` implementation _defines_ the natural order. – Louis Wasserman Nov 25 '14 at 16:30
  • Ok, that makes sense. But the problem still exists, in that it is not using the compareTo that I defined. I can put a break in the code, and it just ignores it. Again this problem only shows up in Java 8 version 20 or greater. – D. Milenski Nov 25 '14 at 16:33
  • What do you mean, you put a break in the code, and it ignores it? There's nothing else that `Collections.sort` _could_ be using. It's possible that if your `List` has size 0 or 1, that your comparator doesn't get called, though. – Louis Wasserman Nov 25 '14 at 16:34
  • I agree, it does not seem possible that It could be using anything else. My list has 4 entries in it. When it sorts the way compareTo says. They are sorted Attendance, Waiting List, Pending Students, Students. After my update to the newest java, it now sorts in alphabetical order, and completely ignores my compareTo code – D. Milenski Nov 25 '14 at 16:44
  • Is it possible that your nodes aren't `FolderNode`s like you appear to expect? Does `FolderNode` define its own `compareTo` method? (Can we see the `FolderNode` class as a whole?) – Louis Wasserman Nov 25 '14 at 16:58
  • Here is FolderNode and the extended class InstructorTreeNode. – D. Milenski Nov 25 '14 at 17:06
  • 1
    I just found the following on Oracle's Web site: Previously Collection.sort copied the elements of the list to sort into an array, sorted that array, then updated list, in place, with those elements in the array, and the default method List.sort deferred to Collection.sort. This was a non-optimal arrangement. From 8u20 release onwards Collection.sort defers to List.sort. This means, for example, existing code that calls Collection.sort with an instance of ArrayList will now use the optimal sort implemented by ArrayList. So do I need to change code to get the same functionality as before? – D. Milenski Nov 25 '14 at 17:26
  • I would be highly shocked if that made a difference to the semantics... – Louis Wasserman Nov 25 '14 at 17:37
  • I agree, but it makes no sense why my functionality would change, just because I updated to a newer version of java. But, I am still kinda new to Java. – D. Milenski Nov 25 '14 at 17:40
  • 1
    My suggestion is that you create a short example program that duplicates the sort problem. This source is too complex for us to diagnose out of context. See if you can create a simple example that demonstrates the issue. – Chuck Krutsinger Feb 26 '15 at 17:30
  • I experienced something similar recently, and I've added a possible explanation in my answer. In my case, overriding the `equals` and `hashCode` methods lead to the correct sorting. – Chthonic Project Feb 27 '15 at 18:55
  • For what attribute you want to sort the InstructorTreeNode. What datatype is it? – Andy_Lima Mar 05 '15 at 15:48

4 Answers4

9

If you use the method Collections#sort(List<T> list), it defers to the method List#sort(Comparator comparator) with comparator given as null. The source code from java.util.Collections is as follows:

public static <T extends Comparable<? super T>> void sort(List<T> list) {
    list.sort(null);
}

If you want to specify your own Comparator, you need to use the method Collections#sort(List<T> list, Comparator<T> comparator), which passes on your comparator to the list sorting method. The source code from java.util.Collections is as follows:

public static <T> void sort(List<T> list, Comparator<? super T> c) {
    list.sort(c);
}

So far so good. Now, as you have correctly pointed out, if you do not specify a comparator, the natural ordering of the class, that is, the compareTo method you have defined, is used.

However, the Comparable class documentation also states the following:

It is strongly recommended (though not required) that natural orderings be consistent with equals. This is so because sorted sets (and sorted maps) without explicit comparators behave "strangely" when they are used with elements (or keys) whose natural ordering is inconsistent with equals. In particular, such a sorted set (or sorted map) violates the general contract for set (or map), which is defined in terms of the equals method.

Since the class InstructorTreeNode does not override Object#equals, your compareTo method may return 0 even if == returns false. I reckon this is leading to what the documentation calls "strangely".

Chthonic Project
  • 8,216
  • 1
  • 43
  • 92
  • OP and her comments suggest that the compareTo method is never called. While your answer explains what should happen, that is not what she is seeing. – Chuck Krutsinger Feb 26 '15 at 17:32
  • @ChuckKrutsinger : Thanks for pointing it out. I experienced something similar just a few weeks ago. Added a possible explanation to my answer (overriding `equals` and `hashCode` resolved the issue). – Chthonic Project Feb 27 '15 at 18:57
  • Excellent addition. That is probably what is happening. +1 – Chuck Krutsinger Feb 27 '15 at 22:53
9

Since Collections.sort now delegates to List.sort, the actual List implementation has an impact. Implementations like ArrayList and Vector take the opportunity to implement List.sort in a more efficient manner than the default implementation as they pass their internal array directly to Arrays.sort omitting the copy steps of the default implementation.

This works seamlessly unless programmers use the anti-pattern of subclassing an implementation (rather than using delegation) overriding methods to implement a contradicting behavior. Lazily populated lists like these from EclipseLink/JPA are known to have problems with this as they try to intercept every reading method to populate the list before proceeding but miss the new sort method. If the list hasn’t populated yet when sort is called, sort will see an empty list state.

In your code, there is no indication where the list does come from and which actual implementation class it has, but since I see a lot of familiar looking annotations, I guess, you are using such a framework…

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • I hadn't considered the lazy fetch aspect. – Chuck Krutsinger Mar 05 '15 at 04:57
  • Confirmed! The problem was the lazy fetch JPA wrapper objects. When fetch was turned to eager, the sort began to work. The sort had always worked up until Java 8u20, so now it seems that JPA lazy fetch mechanism is not compatible with the new Java 8 sort optimizations. – Chuck Krutsinger Mar 06 '15 at 14:29
  • While changing the fetch type to eager fixed the problem, it also created other problems with long waits for the lists to populate. So the solution that I found as a compromise, was to copy the lazy fetched list to a new ArrayList, and then sort that list. Now I get the best of both worlds. – D. Milenski Apr 09 '15 at 20:47
  • My JPA provider is eclipselink. They have recently released version 2.6.0, which fixes the wrapper problem. – D. Milenski Apr 15 '15 at 15:08
  • Thank you!!! My project was a very old one with a very old outdated ORM (Mvcsoft) running on a JBoss 4 and I had incompatiblities running on JDK 8. The custom Comparator not even got called. Repackaging the list delivered by the ORM with a JDK-ArrayList solved the problem. – cljk Sep 22 '17 at 11:44
0

You might not like this answer because it doesn't give you a quick-fix for your situation, but it will help you more in the long run.

This is the kind of bug that you can figure out yourself with a little debugging. I don't know what IDE you are using, but with Eclipse, you can even step into code that is in the JDK!

So, what I would do, is set a breakpoint at the line where you call sort() on the childNodes. Then I would step into the JDK code and just walk through it myself. It will become very clear what is going on and why it isn't calling your compare function.

satnam
  • 10,719
  • 5
  • 32
  • 42
-1

You could try to build a custom comparator. Here is an example how that should look. This is for comparing BigDecimals.

class YourComparator implements Comparator<InstructorTreeNode> {
@Override
public int compare(final  InstructorTreeNode 01, final InstructorTreeNode o2) {
    return o2.getYourCompVal().compareTo(o1.getYourCompVal());
}

}

 public List<InstructorTreeNode> getChildNodes() {
 Collections.sort(childNodes, new YourComparator());
 return childNodes;}
Andy_Lima
  • 129
  • 12