From 1777f6be6664a4e9ae9d52485e0e40edffe25f8c Mon Sep 17 00:00:00 2001 From: Katrina L Hill Date: Sat, 9 Aug 2025 08:56:15 -0700 Subject: [PATCH 1/2] Created a natural sort comparator for the Package Explorer in Java Element Comparator --- .../eclipse/jdt/ui/JavaElementComparator.java | 5 +- .../jdt/ui/NumericalStringComparator.java | 101 ++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java index a44c545e3f7..b2dd8f0220e 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java @@ -259,6 +259,7 @@ public int compare(Viewer viewer, Object e1, Object e2) { if (name1.length() == 0) { if (name2.length() == 0) { try { + return getComparator().compare(((IType) e1).getSuperclassName(), ((IType) e2).getSuperclassName()); } catch (JavaModelException e) { return 0; @@ -271,7 +272,9 @@ public int compare(Viewer viewer, Object e1, Object e2) { } } - int cmp= getComparator().compare(name1, name2); + // allow for sorting by numerical portions when strings are otherwise equal (ignoring file extensions) + NumericalStringComparator comp = new NumericalStringComparator(); + int cmp= comp.compare(name1, name2); if (cmp != 0) { return cmp; } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java new file mode 100644 index 00000000000..5ccb9c3ef5c --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java @@ -0,0 +1,101 @@ +/******************************************************************************* +* Copyright (c) 2015, 2025 Katrina Hill and others. +* +* This program and the accompanying materials +* are made available under the terms of the Eclipse Public License 2.0 +* which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* IBM Corporation - initial API and implementation +* Katrina Hill +*******************************************************************************/ + +package org.eclipse.jdt.ui; + +/** Sorts alphabetically and then by numerical portion of strings having equivalent suffixes after the first '.' + * If suffixes are not equal, falls back to default alphabetical sort + * If prefixes excluding numerical portions are not equal, falls back to alphabetical sort + * If either doesn't contain a numeric portion, it goes before the other + * With all above false, parses numerical portion and sorts by that + * + * Example (unsorted): Test1, Test20, Test3, Test, Test2 + * Default alphabetical sort: Test, Test1, Test2, Test20, Test3 + * Numerical Sort: Test, Test1, Test2, Test3, Test20 + * + * @since 3.36 + */ + +public class NumericalStringComparator { + + public int compare(String s1, String s2) { + int s1CompLength = s1.length(); + int s2CompLength = s2.length(); + //chop off file extensions to restrict comparison to filenames + if(s1.contains(".")) { //$NON-NLS-1$ + s1CompLength = s1.indexOf('.'); + } + if(s2.contains(".")) { //$NON-NLS-1$ + s2CompLength = s2.indexOf('.'); + } + + //sort numerically only if portion after first '.' is equal + String s1Post = s1.substring(s1CompLength); + String s2Post = s2.substring(s2CompLength); + + //only perform numerical sort if suffixes are equal, otherwise default to alphabetical + if(!s1Post.equals(s2Post)) + { + return s1.compareTo(s2); + } + + //isolate numerical portions + String s1Prefix = s1.substring(0, s1CompLength); + String s2Prefix = s2.substring(0, s2CompLength); + + String ints = "0123456789"; //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(); + int index = s1CompLength - 1; + while(ints.contains(String.valueOf(s1.charAt(index)))) + { + sb.append(s1.charAt(index)); + index--; + } + String s1NumStr = sb.reverse().toString(); + sb.setLength(0); + index = s2CompLength - 1; + while(ints.contains(String.valueOf(s2.charAt(index)))) + { + sb.append(s2.charAt(index)); + index--; + } + String s2NumStr = sb.reverse().toString(); + + s1Prefix = s1Prefix.substring(0, s1Prefix.length() - s1NumStr.length()); + s2Prefix = s2Prefix.substring(0, s2Prefix.length() - s2NumStr.length()); + + //if non-numeric portion is sortable, return comparison now + if(s1Prefix.compareTo(s2Prefix) != 0) + return s1Prefix.compareTo(s2Prefix); + + //if non-numeric portion is equal, and suffix after first '.' is equal, compare numerical portions + //if either is empty, return that as first item + if(s1NumStr.length() == 0) + return -1; + + if(s2NumStr.length() == 0) + return 1; + + int s1Int = Integer.parseInt(s1NumStr); + int s2Int = Integer.parseInt(s2NumStr); + + if(s1Int > s2Int) + return 1; + else if(s2Int > s1Int) + return -1; + else + return 0; + } +} From 3557dc3edaea84c272800a7482943c065fa19fe4 Mon Sep 17 00:00:00 2001 From: Jeff Johnston Date: Wed, 10 Sep 2025 19:27:08 -0400 Subject: [PATCH 2/2] Move NumberStringComparator inside JavaElementComparator - remove need for version bump or exposing API --- .../eclipse/jdt/ui/JavaElementComparator.java | 74 ++++++++++++- .../jdt/ui/NumericalStringComparator.java | 101 ------------------ 2 files changed, 73 insertions(+), 102 deletions(-) delete mode 100644 org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java index b2dd8f0220e..980cf76ce44 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/JavaElementComparator.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2000, 2018 IBM Corporation and others. + * Copyright (c) 2000, 2025 IBM Corporation and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -198,6 +198,78 @@ private int getMemberCategory(int kind) { return offset + MEMBERSOFFSET; } + static class NumericalStringComparator { + + public int compare(String s1, String s2) { + int s1CompLength = s1.length(); + int s2CompLength = s2.length(); + //chop off file extensions to restrict comparison to filenames + if(s1.contains(".")) { //$NON-NLS-1$ + s1CompLength = s1.indexOf('.'); + } + if(s2.contains(".")) { //$NON-NLS-1$ + s2CompLength = s2.indexOf('.'); + } + + //sort numerically only if portion after first '.' is equal + String s1Post = s1.substring(s1CompLength); + String s2Post = s2.substring(s2CompLength); + + //only perform numerical sort if suffixes are equal, otherwise default to alphabetical + if(!s1Post.equals(s2Post)) + { + return s1.compareTo(s2); + } + + //isolate numerical portions + String s1Prefix = s1.substring(0, s1CompLength); + String s2Prefix = s2.substring(0, s2CompLength); + + String ints = "0123456789"; //$NON-NLS-1$ + StringBuilder sb = new StringBuilder(); + int index = s1CompLength - 1; + while(ints.contains(String.valueOf(s1.charAt(index)))) + { + sb.append(s1.charAt(index)); + index--; + } + String s1NumStr = sb.reverse().toString(); + sb.setLength(0); + index = s2CompLength - 1; + while(ints.contains(String.valueOf(s2.charAt(index)))) + { + sb.append(s2.charAt(index)); + index--; + } + String s2NumStr = sb.reverse().toString(); + + s1Prefix = s1Prefix.substring(0, s1Prefix.length() - s1NumStr.length()); + s2Prefix = s2Prefix.substring(0, s2Prefix.length() - s2NumStr.length()); + + //if non-numeric portion is sortable, return comparison now + if(s1Prefix.compareTo(s2Prefix) != 0) + return s1Prefix.compareTo(s2Prefix); + + //if non-numeric portion is equal, and suffix after first '.' is equal, compare numerical portions + //if either is empty, return that as first item + if(s1NumStr.length() == 0) + return -1; + + if(s2NumStr.length() == 0) + return 1; + + int s1Int = Integer.parseInt(s1NumStr); + int s2Int = Integer.parseInt(s2NumStr); + + if(s1Int > s2Int) + return 1; + else if(s2Int > s1Int) + return -1; + else + return 0; + } + } + @Override public int compare(Viewer viewer, Object e1, Object e2) { int cat1= category(e1); diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java deleted file mode 100644 index 5ccb9c3ef5c..00000000000 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/ui/NumericalStringComparator.java +++ /dev/null @@ -1,101 +0,0 @@ -/******************************************************************************* -* Copyright (c) 2015, 2025 Katrina Hill and others. -* -* This program and the accompanying materials -* are made available under the terms of the Eclipse Public License 2.0 -* which accompanies this distribution, and is available at -* https://www.eclipse.org/legal/epl-2.0/ -* -* SPDX-License-Identifier: EPL-2.0 -* -* Contributors: -* IBM Corporation - initial API and implementation -* Katrina Hill -*******************************************************************************/ - -package org.eclipse.jdt.ui; - -/** Sorts alphabetically and then by numerical portion of strings having equivalent suffixes after the first '.' - * If suffixes are not equal, falls back to default alphabetical sort - * If prefixes excluding numerical portions are not equal, falls back to alphabetical sort - * If either doesn't contain a numeric portion, it goes before the other - * With all above false, parses numerical portion and sorts by that - * - * Example (unsorted): Test1, Test20, Test3, Test, Test2 - * Default alphabetical sort: Test, Test1, Test2, Test20, Test3 - * Numerical Sort: Test, Test1, Test2, Test3, Test20 - * - * @since 3.36 - */ - -public class NumericalStringComparator { - - public int compare(String s1, String s2) { - int s1CompLength = s1.length(); - int s2CompLength = s2.length(); - //chop off file extensions to restrict comparison to filenames - if(s1.contains(".")) { //$NON-NLS-1$ - s1CompLength = s1.indexOf('.'); - } - if(s2.contains(".")) { //$NON-NLS-1$ - s2CompLength = s2.indexOf('.'); - } - - //sort numerically only if portion after first '.' is equal - String s1Post = s1.substring(s1CompLength); - String s2Post = s2.substring(s2CompLength); - - //only perform numerical sort if suffixes are equal, otherwise default to alphabetical - if(!s1Post.equals(s2Post)) - { - return s1.compareTo(s2); - } - - //isolate numerical portions - String s1Prefix = s1.substring(0, s1CompLength); - String s2Prefix = s2.substring(0, s2CompLength); - - String ints = "0123456789"; //$NON-NLS-1$ - StringBuilder sb = new StringBuilder(); - int index = s1CompLength - 1; - while(ints.contains(String.valueOf(s1.charAt(index)))) - { - sb.append(s1.charAt(index)); - index--; - } - String s1NumStr = sb.reverse().toString(); - sb.setLength(0); - index = s2CompLength - 1; - while(ints.contains(String.valueOf(s2.charAt(index)))) - { - sb.append(s2.charAt(index)); - index--; - } - String s2NumStr = sb.reverse().toString(); - - s1Prefix = s1Prefix.substring(0, s1Prefix.length() - s1NumStr.length()); - s2Prefix = s2Prefix.substring(0, s2Prefix.length() - s2NumStr.length()); - - //if non-numeric portion is sortable, return comparison now - if(s1Prefix.compareTo(s2Prefix) != 0) - return s1Prefix.compareTo(s2Prefix); - - //if non-numeric portion is equal, and suffix after first '.' is equal, compare numerical portions - //if either is empty, return that as first item - if(s1NumStr.length() == 0) - return -1; - - if(s2NumStr.length() == 0) - return 1; - - int s1Int = Integer.parseInt(s1NumStr); - int s2Int = Integer.parseInt(s2NumStr); - - if(s1Int > s2Int) - return 1; - else if(s2Int > s1Int) - return -1; - else - return 0; - } -}