Skip to content

Commit 4b7d073

Browse files
Merge pull request #5647 from Baaaaaz/feature/5616-initiative-sort-order-improvements
Enchanced Token Initiative sorting.
2 parents afb31a9 + c175766 commit 4b7d073

File tree

1 file changed

+111
-42
lines changed

1 file changed

+111
-42
lines changed

src/main/java/net/rptools/maptool/model/InitiativeList.java

Lines changed: 111 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
import java.io.Serializable;
2323
import java.util.*;
2424
import java.util.concurrent.ExecutionException;
25+
import java.util.regex.Matcher;
26+
import java.util.regex.Pattern;
2527
import java.util.stream.Collectors;
2628
import javax.swing.Icon;
2729
import net.rptools.maptool.client.AppPreferences;
@@ -476,59 +478,30 @@ public void update() {
476478

477479
/**
478480
* Sort the tokens by their initiative state according to the default, descending order. See
479-
* {@link #sort(boolean)} for more details on handling of strings and nulls.
481+
* {@link #sort(boolean)} for more details.
482+
*
483+
* @see TokenInitiativeDescComparator
480484
*/
481485
public void sort() {
482486
this.sort(false);
483487
}
484488

485489
/**
486-
* Sort the tokens by their initiative state, in either ascending or descending order. If the
487-
* initiative state string can be converted into a {@link Double} that is done first. All values
488-
* converted to {@link Double}s are always considered bigger than the {@link String} values. The
489-
* {@link String} values are considered bigger than any <code>null</code> values.
490+
* Sort the tokens by their initiative state using a {@link Comparator}
491+
*
492+
* @param ascendingOrder
493+
* @see TokenInitiativeDescComparator
490494
*/
491495
public void sort(boolean ascendingOrder) {
492496
startUnitOfWork();
493-
final int DIRECTION = ascendingOrder ? -1 : 1;
494497
TokenInitiative currentInitiative =
495498
getTokenInitiative(getCurrent()); // Save the currently selected initiative
496-
tokens.sort(
497-
(o1, o2) -> {
498-
499-
// Get a number, string, or null for first parameter
500-
Object one = null;
501-
if (o1.state != null) {
502-
one = o1.state;
503-
try {
504-
one = Double.valueOf(o1.state);
505-
} catch (NumberFormatException e) {
506-
// Not a number so ignore
507-
} // endtry
508-
} // endif
509-
510-
// Repeat for second param
511-
Object two = null;
512-
if (o2.state != null) {
513-
two = o2.state;
514-
try {
515-
two = Double.valueOf(o2.state);
516-
} catch (NumberFormatException e) {
517-
// Not a number so ignore
518-
} // endtry
519-
} // endif
520-
521-
// Do the comparison
522-
if (Objects.equals(one, two)) return 0;
523-
if (one == null) return 1 * DIRECTION; // Null is always the smallest value
524-
if (two == null) return -1 * DIRECTION;
525-
if (one instanceof Double & two instanceof Double)
526-
return ((Double) two).compareTo((Double) one) * DIRECTION;
527-
if (one instanceof String & two instanceof String)
528-
return ((String) two).compareTo((String) one) * DIRECTION;
529-
if (one instanceof Double) return -1 * DIRECTION; // Integers are bigger than strings
530-
return 1 * DIRECTION;
531-
});
499+
500+
Collections.sort(tokens, new TokenInitiativeDescComparator());
501+
if (ascendingOrder) {
502+
Collections.reverse(tokens);
503+
}
504+
532505
getPCS().firePropertyChange(TOKENS_PROP, null, tokens);
533506
setCurrent(indexOf(currentInitiative)); // Restore current initiative
534507
finishUnitOfWork();
@@ -1001,4 +974,100 @@ public TokenInitiativeDto toDto() {
1001974
return dto.build();
1002975
}
1003976
}
977+
978+
/*---------------------------------------------------------------------------------------------
979+
* TokenInitiativeComparator Inner Class
980+
*-------------------------------------------------------------------------------------------*/
981+
982+
/**
983+
* Comparator in a helper class to control the ordering of the TokenInitiative list.
984+
*
985+
* <p>Uses regex {@link Pattern} and {@link Matcher} to try and extract a leading number (to
986+
* convert to a {@link Double}) and/or a trailing string from the token's initiative state. Then
987+
* it compares in descending fashion by:
988+
*
989+
* <ol>
990+
* <li>{@link Double}s are always considered bigger than the {@link String} values.
991+
* <li>{@link String} values are considered bigger than any <code>null</code> values.
992+
* <li>if all else is the same, finally compare by token name and then by token ID to get a
993+
* deterministic order to prevent flip-flopping on repeated sorts and/or tokens not changing
994+
* order when sort order is reversed.
995+
* </ol>
996+
*
997+
* @see TokenInitiative
998+
*/
999+
public class TokenInitiativeDescComparator implements Comparator<TokenInitiative> {
1000+
@Override
1001+
public int compare(TokenInitiative o1, TokenInitiative o2) {
1002+
Pattern p = Pattern.compile("^(-?\\d+\\.?\\d*)?(.*)");
1003+
1004+
// Get a number and/or string, or a null for first parameter
1005+
Object one = o1.getState();
1006+
Double oneDouble = null;
1007+
String oneString = null;
1008+
try {
1009+
Matcher m1 = p.matcher(o1.state);
1010+
if (m1.matches()) {
1011+
oneString = m1.group(2).trim();
1012+
oneDouble = Double.valueOf(m1.group(1));
1013+
}
1014+
} catch (NumberFormatException | NullPointerException e) {
1015+
// either null or could not convert to a Double
1016+
}
1017+
1018+
// Repeat for second param
1019+
Object two = o2.getState();
1020+
Double twoDouble = null;
1021+
String twoString = null;
1022+
try {
1023+
Matcher m2 = p.matcher(o2.state);
1024+
if (m2.matches()) {
1025+
twoString = m2.group(2).trim();
1026+
twoDouble = Double.valueOf(m2.group(1));
1027+
}
1028+
} catch (NumberFormatException | NullPointerException e) {
1029+
// either null or could not convert to a Double
1030+
}
1031+
1032+
int comparison = 0;
1033+
if (Objects.equals(one, two)) {
1034+
comparison = 0;
1035+
} else if (one == null) {
1036+
comparison = 1; // nulls come last
1037+
} else if (two == null) {
1038+
comparison = -1;
1039+
} else if (!Objects.equals(twoDouble, oneDouble)) {
1040+
// Numbers are different
1041+
if (oneDouble == null) {
1042+
comparison = 1;
1043+
} else if (twoDouble == null) {
1044+
comparison = -1;
1045+
} else {
1046+
comparison = twoDouble.compareTo(oneDouble);
1047+
}
1048+
} else {
1049+
// Numbers are the same, so sort by strings
1050+
if (oneString == null) {
1051+
comparison = 1;
1052+
} else if (twoString == null) {
1053+
comparison = -1;
1054+
} else {
1055+
comparison = twoString.compareToIgnoreCase(oneString);
1056+
if (comparison == 0) {
1057+
comparison = twoString.compareTo(oneString);
1058+
}
1059+
}
1060+
}
1061+
1062+
// If still tied, compare the names and then the GUID to get a deterministic sort order.
1063+
if (comparison == 0) {
1064+
comparison = o1.getToken().getName().compareTo(o2.getToken().getName());
1065+
if (comparison == 0) {
1066+
comparison = o1.getToken().getId().compareTo(o2.getToken().getId());
1067+
}
1068+
}
1069+
1070+
return comparison;
1071+
}
1072+
}
10041073
}

0 commit comments

Comments
 (0)