mirror of
https://github.com/NoCheatPlus/NoCheatPlus.git
synced 2024-09-27 14:13:11 +02:00
[BLEEDING] Prepare internal registry lists overhaul to support sorting.
* Add RegistrationOrder for order. * Add sorting functionality with some tests. (See RegistrationOrder.AbstractRegisTrationOrderSort.) * (Except for test cases, all this is not yet in use.) (Perhaps the sorting should be changed to use an array for output instead of iterators, this just represents a quick way in.) Further direction: * Replace all the registry lists like for INotifyReload and IDisableListener and so on within registries by a generic storage that uses this for sorting. This way every such thing will support both priority-based and tag-based order at the same time. * To support different order definitions depending on what type an interface like IRegisterWithOrder might be supported by a registry. * Later generic event listeners might also use this, simply.
This commit is contained in:
parent
97cdbe8046
commit
d0c42ab061
@ -0,0 +1,37 @@
|
||||
package fr.neatmonster.nocheatplus.components.registry.exception;
|
||||
|
||||
/**
|
||||
* A registry item has already been registered (for a given context), and the
|
||||
* registry does not support overriding via register(...) - it might still
|
||||
* support unregister(...) and register(...), in case what to unregister is
|
||||
* known.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
*/
|
||||
public class AlreadyRegisteredException extends RegistryException {
|
||||
|
||||
private static final long serialVersionUID = -72557863263954102L;
|
||||
|
||||
public AlreadyRegisteredException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public AlreadyRegisteredException(String message, Throwable cause, boolean enableSuppression,
|
||||
boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
public AlreadyRegisteredException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public AlreadyRegisteredException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public AlreadyRegisteredException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package fr.neatmonster.nocheatplus.components.registry.order;
|
||||
|
||||
/**
|
||||
* Just provide a neutral getter, independent of what context it is used in.
|
||||
* There will be confusion potential, so this remains subject to an overhaul
|
||||
* later on.
|
||||
* <hr>
|
||||
* Typical uses:
|
||||
* <ul>
|
||||
* <li>IRegisterWithOrder might just extend this one, renaming and adding more
|
||||
* methods pending there.</li>
|
||||
* <li>IGetRegistrationOrder can be implemented to enable use of sorting
|
||||
* methods.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
*/
|
||||
public interface IGetRegistrationOrder {
|
||||
|
||||
public RegistrationOrder getRegistrationOrder();
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package fr.neatmonster.nocheatplus.components.registry.order;
|
||||
|
||||
public interface IRegisterWithOrder {
|
||||
|
||||
public RegistrationOrder getRegistrationOrder(Class<?> registerForType);
|
||||
// TODO: getRegistrationOrder(Class<?> registerForType, Class<?> registryType) ?
|
||||
}
|
@ -17,7 +17,9 @@ package fr.neatmonster.nocheatplus.components.registry.order;
|
||||
import java.util.Comparator;
|
||||
|
||||
/**
|
||||
* Utilities for sorting out order.
|
||||
* Utilities for sorting out order. <br>
|
||||
* TODO: DEPRECATE and later remove.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
*/
|
||||
|
@ -0,0 +1,23 @@
|
||||
package fr.neatmonster.nocheatplus.components.registry.order;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Empty string counts as null.
|
||||
* @author asofold
|
||||
*
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface RegisterWithOrder {
|
||||
/** Crude workaround for an Integer that may be null. */
|
||||
public String basePriority() default "";
|
||||
public String tag() default "";
|
||||
public String beforeTag() default "";
|
||||
public String afterTag() default "";
|
||||
}
|
@ -0,0 +1,577 @@
|
||||
package fr.neatmonster.nocheatplus.components.registry.order;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import fr.neatmonster.nocheatplus.utilities.ds.map.CoordHash;
|
||||
|
||||
/**
|
||||
* The order of registration. These objects can't be sorted reliably, due to
|
||||
* tags allowing virtually everything, thus specific (greedy) rules are needed.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
*/
|
||||
public class RegistrationOrder {
|
||||
|
||||
/**
|
||||
* Compare on base of basePriority. Entries with null priority are sorted to
|
||||
* the front.
|
||||
*/
|
||||
public static Comparator<RegistrationOrder> cmpBasePriority = new Comparator<RegistrationOrder>() {
|
||||
@Override
|
||||
public int compare(final RegistrationOrder o1, final RegistrationOrder o2) {
|
||||
final Integer p1 = o1.getBasePriority();
|
||||
final Integer p2 = o2.getBasePriority();
|
||||
if (p1 == null) {
|
||||
return p2 == null ? 0 : -1; // o1 to front.
|
||||
} else if (p2 == null) {
|
||||
return 1; // o2 to front.
|
||||
} else {
|
||||
return p1.compareTo(p2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Auxiliary class to contain the basic sorting algorithm, using an abstract
|
||||
* method to fetch the RegistrationOrder from the input type. The sorting
|
||||
* isn't necessarily stable, and tag-specific order can vary wildly
|
||||
* depending on the order of input.
|
||||
* <hr>
|
||||
* The basePriority comes first on sorting, beforeTag set means being put to
|
||||
* front of a priority level, afterTag means being sorted to the end (with
|
||||
* beforeTag still in front of the priority level, without to the end). With
|
||||
* null priority entries are sorted to the very start/end of the list,
|
||||
* unless both of beforeTag and afterTag are null, in which case they're
|
||||
* sorted to/around the level of basePriority being 0. Tag-based comparison
|
||||
* uses regular expressions via tagA.matchhes(beforeTagB|afterTagB). The
|
||||
* element sorted into an existing (sub-) list has their beforeTag/afterTag
|
||||
* checked first (greedy).<br>
|
||||
* TODO: Describe the sorting algorithm in more detail, if needed.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
* @param <F>
|
||||
*/
|
||||
public static abstract class AbstractRegistrationOrderSort<F> {
|
||||
|
||||
// TODO: Signature with passing IFetchRegistrationOrder<F> to the sorting?
|
||||
// TODO: Back to generic static methods?
|
||||
|
||||
private final Comparator<F> cmp = new Comparator<F>() {
|
||||
@Override
|
||||
public int compare(final F o1, final F o2) {
|
||||
return RegistrationOrder.cmpBasePriority.compare(
|
||||
fetchRegistrationOrder(o1), fetchRegistrationOrder(o2));
|
||||
}
|
||||
};
|
||||
|
||||
protected abstract RegistrationOrder fetchRegistrationOrder(F item);
|
||||
|
||||
public void sort(final Collection<F> input) {
|
||||
final LinkedList<F> sorted = getSortedLinkedList(input);
|
||||
input.clear();
|
||||
input.addAll(sorted);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public F[] getSortedArray(Collection<F> input) {
|
||||
// TODO: Make the default sorting thing use an array (!).
|
||||
return (F[]) getSortedLinkedList(input).toArray();
|
||||
}
|
||||
|
||||
public final <O extends Collection<F>> O sort(final Collection<F> input, final O output) {
|
||||
output.addAll(getSortedLinkedList(input));
|
||||
return output;
|
||||
}
|
||||
|
||||
public LinkedList<F> getSortedLinkedList(final Collection<F> input) {
|
||||
/*
|
||||
* (Due to the need for a ListIterator, you can't just use a given
|
||||
* type that merely extends Collection.)
|
||||
*/
|
||||
// TODO: Implement using an array for output with insertionIndex (less iterators etc).
|
||||
final LinkedList<F> output = new LinkedList<F>();
|
||||
if (input == null || input.isEmpty()) {
|
||||
return output;
|
||||
}
|
||||
else if (input.size() == 1) {
|
||||
output.addAll(input);
|
||||
return output;
|
||||
}
|
||||
// Sort into rough groups.
|
||||
final LinkedList<F> belowZeroPriority = new LinkedList<F>();
|
||||
final LinkedList<F> zeroPriority = new LinkedList<F>();
|
||||
final LinkedList<F> aboveZeroPriority = new LinkedList<F>();
|
||||
for (final F item : input) {
|
||||
final RegistrationOrder order = fetchRegistrationOrder(item);
|
||||
final Integer basePriority = order.getBasePriority();
|
||||
if (basePriority == null) {
|
||||
// Distinguish cases, note the sorting order of cmpBasePriority.
|
||||
if (order.getBeforeTag() != null) {
|
||||
/*
|
||||
* These will be sorted to front, processed last within
|
||||
* this list, resulting in optimal order.
|
||||
*/
|
||||
belowZeroPriority.add(item);
|
||||
}
|
||||
else if (order.getAfterTag() != null) {
|
||||
// These will be last in the end, thus add to tempOut directly.
|
||||
sortInFromStart(output, item);
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* These are somehow added to 0-priority - could end up
|
||||
* anywhere, if 0-basePriority items have an afterTag
|
||||
* set that matches the tag. Consequently items with
|
||||
* null base priority are processed last.
|
||||
*/
|
||||
zeroPriority.add(item);
|
||||
}
|
||||
}
|
||||
else if (basePriority < 0) {
|
||||
belowZeroPriority.add(item);
|
||||
}
|
||||
else if (basePriority > 0) {
|
||||
aboveZeroPriority.add(item);
|
||||
}
|
||||
else {
|
||||
// Ensure to process items with 0 basePriority set first.
|
||||
zeroPriority.add(0, item);
|
||||
}
|
||||
}
|
||||
// Combine lists into output.
|
||||
// TODO: Perhaps could use optimized methods for sorting in later on.
|
||||
if (!aboveZeroPriority.isEmpty()) {
|
||||
addSortedSubList(aboveZeroPriority, output);
|
||||
}
|
||||
if (!zeroPriority.isEmpty()) {
|
||||
// Still need to sort the roughly pre-grouped items, to match tags.
|
||||
for (final F item : zeroPriority) {
|
||||
sortInFromStart(output, item);
|
||||
}
|
||||
}
|
||||
if (!belowZeroPriority.isEmpty()) {
|
||||
addSortedSubList(belowZeroPriority, output);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort, reverse and then add the subList to the output via
|
||||
* sortInFromStart.
|
||||
*
|
||||
* @param subList
|
||||
* @param output
|
||||
*/
|
||||
private void addSortedSubList(final List<F> subList, final List<F> output) {
|
||||
Collections.sort(subList, cmp);
|
||||
Collections.reverse(subList);
|
||||
for (final F item : subList) {
|
||||
sortInFromStart(output, item);
|
||||
}
|
||||
}
|
||||
|
||||
private void sortInFromStart(final List<F> list, final F item) {
|
||||
// Attempt to get the fast thing first.
|
||||
if (list.isEmpty()) {
|
||||
list.add(item);
|
||||
return;
|
||||
}
|
||||
final RegistrationOrder order = fetchRegistrationOrder(item);
|
||||
if (shouldSortBefore(order, fetchRegistrationOrder(list.get(0)))) {
|
||||
list.add(0, item);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
sortInFromSecond(list, item, order);
|
||||
}
|
||||
}
|
||||
|
||||
private void sortInFromSecond(final List<F> list, final F item, final RegistrationOrder order) {
|
||||
final ListIterator<F> it = list.listIterator(1);
|
||||
while (it.hasNext()) {
|
||||
final F otherItem = it.next();
|
||||
final RegistrationOrder other = fetchRegistrationOrder(otherItem);
|
||||
if (shouldSortBefore(order, other)) {
|
||||
it.previous();
|
||||
it.add(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// To the end.
|
||||
it.add(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* The heart of all the sorting. See {@link AbstractRegistrationOrderSort} for a description.
|
||||
* @param order1
|
||||
* @param order2
|
||||
* @return
|
||||
*/
|
||||
public static boolean shouldSortBefore(final RegistrationOrder order1, final RegistrationOrder order2) {
|
||||
// TODO: Javadocs - where / how.
|
||||
final Integer basePriority1 = order1.getBasePriority();
|
||||
final String tag1 = order1.getTag();
|
||||
final String beforeTag1 = order1.getBeforeTag();
|
||||
final String afterTag1 = order1.getAfterTag();
|
||||
final Integer basePriority2 = order2.getBasePriority();
|
||||
final String tag2 = order2.getTag();
|
||||
final String beforeTag2 = order2.getBeforeTag();
|
||||
final String afterTag2 = order2.getAfterTag();
|
||||
// TODO: Can the tag comparison be unified (simplification)?
|
||||
// TODO: Final decision: first all tags of the first, or first 'before' each then 'after' each?
|
||||
if (basePriority1 == null) {
|
||||
if (basePriority2 == null) {
|
||||
if (beforeTag1 == null) {
|
||||
if (beforeTag2 == null) {
|
||||
if (afterTag1 == null) {
|
||||
// Safe to abort here.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (
|
||||
// order1 might be set to come after order2.
|
||||
tag2 != null && tag2.matches(afterTag1)
|
||||
// Must proceed to check other entries with afterTag set.
|
||||
|| afterTag2 == null
|
||||
|| tag1 == null
|
||||
// The other entry is not set to come after this one.
|
||||
|| !tag1.matches(afterTag2)) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* beforeTag2 is not null - the only way to let
|
||||
* order1 come first, is to have tag1 match
|
||||
* afterTag2. Otherwise
|
||||
*/
|
||||
return tag1 != null && afterTag2 != null && tag1.matches(afterTag2);
|
||||
}
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* beforeTag1 is not null - the only way to let
|
||||
* order2 come first, is to have tag2 match
|
||||
* afterTag1. Otherwise
|
||||
*/
|
||||
return tag2 == null || afterTag1 == null || !tag2.matches(afterTag1);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// order2 has basePriority set, proceed depending on tags.
|
||||
return
|
||||
// Pre-any-basePriority region.
|
||||
beforeTag1 != null
|
||||
// To center region (simple).
|
||||
|| afterTag1 == null && basePriority2 > 0
|
||||
// To center region (with comparison).
|
||||
|| afterTag1 == null && basePriority2 == 0
|
||||
&& (
|
||||
// Sort to the end of 0-basePriority, where neither beforeTag nor afterTag is set.
|
||||
tag1 == null && (beforeTag2 == null && afterTag2 != null)
|
||||
// The other is explicitly set to be later.
|
||||
|| tag1 != null && afterTag2 != null && tag1.matches(afterTag2)
|
||||
// Before anything that only has the afterTag set, otherwise.
|
||||
|| beforeTag2 == null && afterTag2 != null
|
||||
)
|
||||
;
|
||||
}
|
||||
}
|
||||
else if (basePriority2 == null) {
|
||||
// Determine region to fit in (...).
|
||||
if (beforeTag2 == null) {
|
||||
if (afterTag2 == null) {
|
||||
// order2 is within the 0-basePriority region.
|
||||
if (basePriority1 < 0) {
|
||||
return false;
|
||||
}
|
||||
else if (basePriority1 > 0) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Both are within the same region.
|
||||
if (tag2 != null && afterTag1 != null && tag2.matches(afterTag1)) {
|
||||
// order1 is set to come after order2.
|
||||
return false;
|
||||
}
|
||||
else if (beforeTag1 != null) {
|
||||
// Order1 comes first.
|
||||
return true;
|
||||
}
|
||||
else if (afterTag1 != null) {
|
||||
// Order1 is sorted to the end of the region rather.
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
/*
|
||||
* Prefer to have have set basePriority 0 first,
|
||||
* assuming null basePriority to be interpreted
|
||||
* as 'later registered', keeping the order of
|
||||
* registration rather.
|
||||
*/
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// order2 comes later.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// order2 comes first.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Both not null.
|
||||
if (basePriority1 < basePriority2) {
|
||||
return true;
|
||||
}
|
||||
else if (basePriority1 > basePriority2) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// Same priority set, distinguish by tag settings.
|
||||
if (beforeTag1 == null) {
|
||||
if (beforeTag2 == null) {
|
||||
// Only distinction could be afterTagS.
|
||||
if (afterTag1 == null) {
|
||||
// Either can't distinguish, or order2 should come afterwards.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
if (afterTag2 == null) {
|
||||
// order1 should be passed on.
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// Both afterTagS are not null.
|
||||
if (tag2 != null && tag2.matches(afterTag1)) {
|
||||
// Greedy (could override afterTag2).
|
||||
return false;
|
||||
}
|
||||
else if (tag1 != null && tag1.matches(afterTag2)) {
|
||||
// order2 is set to come after order1.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// order1 may be set to come after another entry that follows.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// beforeTag1 is null, beforeTag2 not.
|
||||
if (afterTag2 != null && tag1 != null && tag1.matches(afterTag2)) {
|
||||
// order2 is set to come after order1,
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// beforeTag set rules otherwise.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// beforeTag1 is null.
|
||||
if (beforeTag2 == null) {
|
||||
if (afterTag1 != null && tag2 != null && tag2.matches(afterTag1)) {
|
||||
// order1 is explicitly set to come after order2.
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// TODO: Very complicated - explain.
|
||||
return afterTag2 == null && afterTag1 == null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Both beforeTag1 and beforeTag2 are not so null.
|
||||
if (tag2 != null && tag2.matches(beforeTag1)) {
|
||||
// order1 is set to come before order2.
|
||||
return true;
|
||||
}
|
||||
else if (tag1 != null && tag1.matches(beforeTag2)) {
|
||||
// order2 is set to come before order1.
|
||||
return false;
|
||||
}
|
||||
else if (afterTag1 == null) {
|
||||
// Either can't distinguish, or afterTag2 is set, thus sort in before.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// TODO: If correct, simplify (one condition returns true).
|
||||
// afterTag1 is not null.
|
||||
if (tag2 != null && tag2.matches(afterTag1)) {
|
||||
// order1 is set to come after order2.
|
||||
return false;
|
||||
}
|
||||
else if (afterTag2 != null){
|
||||
if (tag1 != null && tag1.matches(afterTag2)) {
|
||||
// order2 is set to come after order1.
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
// Rather keep checking.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// afterTag1 is null, otherwise not matching, thus order1 comes behind.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// (No catch all.)
|
||||
}
|
||||
}
|
||||
|
||||
public static class SortRegistrationOrder extends AbstractRegistrationOrderSort<RegistrationOrder> {
|
||||
@Override
|
||||
protected RegistrationOrder fetchRegistrationOrder(RegistrationOrder item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SortIGetRegistrationOrder extends AbstractRegistrationOrderSort<IGetRegistrationOrder> {
|
||||
@Override
|
||||
protected RegistrationOrder fetchRegistrationOrder(IGetRegistrationOrder item) {
|
||||
return item.getRegistrationOrder();
|
||||
}
|
||||
}
|
||||
|
||||
/** The default order (no preference, middle). */
|
||||
public static final RegistrationOrder DEFAULT_ORDER = new RegistrationOrder();
|
||||
|
||||
/**
|
||||
* Sort collections of RegistrationOrder instances.
|
||||
*/
|
||||
public static final SortRegistrationOrder sortRegistrationOrder = new SortRegistrationOrder();
|
||||
|
||||
/**
|
||||
* Sort collections of IGetRegistrationOrder instances.
|
||||
*/
|
||||
public static final SortIGetRegistrationOrder sortIGetRegistrationOrder = new SortIGetRegistrationOrder();
|
||||
|
||||
/*
|
||||
* TODO: Consider a registration id / count (meant unique, increasing with
|
||||
* the next registration). When both priorities are 0, this id could be used
|
||||
* to guarantee order.
|
||||
*/
|
||||
private final Integer basePriority;
|
||||
private final String tag;
|
||||
private final String beforeTag; // TODO: Set ?
|
||||
private final String afterTag; // TODO: Set ?
|
||||
|
||||
/**
|
||||
*
|
||||
* @param bluePrint
|
||||
* @throws NumberFormatException, if basePriority can't be parsed.
|
||||
*/
|
||||
public RegistrationOrder(RegisterWithOrder bluePrint) {
|
||||
// TODO: InvalidOrderException via static method for parsing.
|
||||
this(bluePrint.basePriority().isEmpty() ? null : Integer.parseInt(bluePrint.basePriority()),
|
||||
bluePrint.tag().isEmpty() ? null : bluePrint.tag(),
|
||||
bluePrint.beforeTag().isEmpty() ? null : bluePrint.beforeTag(),
|
||||
bluePrint.afterTag().isEmpty() ? null : bluePrint.afterTag());
|
||||
}
|
||||
|
||||
public RegistrationOrder(RegistrationOrder bluePrint) {
|
||||
this(bluePrint.getBasePriority(), bluePrint.getTag(), bluePrint.getBeforeTag(), bluePrint.getAfterTag());
|
||||
}
|
||||
|
||||
public RegistrationOrder() {
|
||||
this(null, null, null, null);
|
||||
}
|
||||
|
||||
public RegistrationOrder(Integer basePriority) {
|
||||
this(basePriority, null, null, null);
|
||||
}
|
||||
|
||||
public RegistrationOrder(Integer basePriority, String tag) {
|
||||
this(basePriority, tag, null, null);
|
||||
}
|
||||
|
||||
public RegistrationOrder(String tag) {
|
||||
this(null, tag, null, null);
|
||||
}
|
||||
|
||||
public RegistrationOrder(String tag, String beforeTag, String afterTag) {
|
||||
this(null, tag, beforeTag, afterTag);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param basePriority
|
||||
* Basic priority for sorting (first priority). If this is not
|
||||
* set (i.e. is null), beforeTag and afterTag allow sorting
|
||||
* independently of the basePriority of other instances, however
|
||||
* the sorting is greedy/special.
|
||||
* @param tag
|
||||
* @param beforeTag
|
||||
* @param afterTag
|
||||
*/
|
||||
public RegistrationOrder(Integer basePriority, String tag, String beforeTag, String afterTag) {
|
||||
this.basePriority = basePriority;
|
||||
this.tag = tag;
|
||||
this.beforeTag = beforeTag;
|
||||
this.afterTag = afterTag;
|
||||
}
|
||||
|
||||
public Integer getBasePriority() {
|
||||
return basePriority;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public String getBeforeTag() {
|
||||
return beforeTag;
|
||||
}
|
||||
|
||||
public String getAfterTag() {
|
||||
return afterTag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// (Special value for null.)
|
||||
return (basePriority == null ? CoordHash.hashCode3DPrimes(-1, -1, -1) : basePriority.hashCode())
|
||||
^ (tag == null ? CoordHash.hashCode3DPrimes(1, 0, 0) : tag.hashCode())
|
||||
^ (beforeTag == null ? CoordHash.hashCode3DPrimes(0, 1, 0) : beforeTag.hashCode())
|
||||
^ (afterTag == null ? CoordHash.hashCode3DPrimes(0, 0, 1) : afterTag.hashCode()) ;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
if (obj == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof RegistrationOrder)) {
|
||||
return false;
|
||||
}
|
||||
final RegistrationOrder other = (RegistrationOrder) obj;
|
||||
return (basePriority == null ? other.getBasePriority() == null : basePriority.equals(other.getBasePriority()))
|
||||
&& (tag == null ? other.getTag() == null : tag.equals(other.getTag()))
|
||||
&& (beforeTag == null ? other.getBeforeTag() == null : beforeTag.equals(other.getBeforeTag()))
|
||||
&& (afterTag == null ? other.getAfterTag() == null : afterTag.equals(other.getAfterTag()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -21,13 +21,15 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Priority for order during setup/initialization of something.
|
||||
* @author mc_dev
|
||||
* Priority for order during setup/initialization of something. <br>
|
||||
* TODO: DEPRECATE, later remove.
|
||||
*
|
||||
* @author asofold
|
||||
*
|
||||
*/
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SetupOrder {
|
||||
public int priority() default 0;
|
||||
public int priority() default 0;
|
||||
}
|
||||
|
@ -0,0 +1,360 @@
|
||||
package fr.neatmonster.nocheatplus.test;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import fr.neatmonster.nocheatplus.components.registry.order.IGetRegistrationOrder;
|
||||
import fr.neatmonster.nocheatplus.components.registry.order.RegistrationOrder;
|
||||
import fr.neatmonster.nocheatplus.utilities.StringUtil;
|
||||
import fr.neatmonster.nocheatplus.utilities.build.BuildParameters;
|
||||
|
||||
public class TestRegistrationOrder {
|
||||
|
||||
public static interface IAccessSort<F> {
|
||||
public LinkedList<F> getSortedLinkedList(Collection<F> items);
|
||||
public RegistrationOrder getRegistrationOrder(F item);
|
||||
}
|
||||
|
||||
public static final IAccessSort<RegistrationOrder> accessRegistrationOrder = new IAccessSort<RegistrationOrder>() {
|
||||
@Override
|
||||
public LinkedList<RegistrationOrder> getSortedLinkedList(Collection<RegistrationOrder> items) {
|
||||
return RegistrationOrder.sortRegistrationOrder.getSortedLinkedList(items);
|
||||
}
|
||||
@Override
|
||||
public RegistrationOrder getRegistrationOrder(RegistrationOrder item) {
|
||||
return item;
|
||||
}
|
||||
};
|
||||
|
||||
public static final class AccessIGetRegistrationOrder implements IAccessSort<IGetRegistrationOrder> {
|
||||
@Override
|
||||
public LinkedList<IGetRegistrationOrder> getSortedLinkedList(Collection<IGetRegistrationOrder> items) {
|
||||
return RegistrationOrder.sortIGetRegistrationOrder.getSortedLinkedList(items);
|
||||
}
|
||||
@Override
|
||||
public RegistrationOrder getRegistrationOrder(IGetRegistrationOrder item) {
|
||||
return item.getRegistrationOrder();
|
||||
}
|
||||
}
|
||||
|
||||
public static final IAccessSort<IGetRegistrationOrder> accessIGetRegistrationOrder = new AccessIGetRegistrationOrder();
|
||||
|
||||
private static String[] tags = new String[]{null, "foo", "bar", "low", "high", "ledge", "bravo", "bravissimo"};
|
||||
|
||||
|
||||
|
||||
private final Random random = new Random(System.currentTimeMillis() ^ System.nanoTime());
|
||||
|
||||
@Test
|
||||
public void testRegistrationOrder() {
|
||||
|
||||
int repeatShuffle = 15;
|
||||
int rand1samples = BuildParameters.testLevel > 0 ? 500 : 50;
|
||||
final List<List<RegistrationOrder>> samples = new ArrayList<List<RegistrationOrder>>();
|
||||
// TODO: Hand crafted, random, part-random (defined tags, random order).
|
||||
for (int i = 0; i < rand1samples; i++) {
|
||||
samples.add(getSample(5, 12)); // Random-ish tests to have something in place.
|
||||
}
|
||||
|
||||
// Iterate ...
|
||||
for (List<RegistrationOrder> sample : samples) {
|
||||
List<IGetRegistrationOrder> sample2 = new ArrayList<IGetRegistrationOrder>();
|
||||
for (final RegistrationOrder order : sample) {
|
||||
sample2.add(new IGetRegistrationOrder() {
|
||||
@Override
|
||||
public RegistrationOrder getRegistrationOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
// Test sorting each.
|
||||
testSorting(sample, accessRegistrationOrder, repeatShuffle);
|
||||
testSorting(sample2, accessIGetRegistrationOrder, repeatShuffle);
|
||||
|
||||
// Test equality.
|
||||
testEquality(sample, accessRegistrationOrder, sample2, accessIGetRegistrationOrder, repeatShuffle);
|
||||
|
||||
}
|
||||
// TODO: Create RegistrationOrder and IGetRegistrationOrder inputs equally.
|
||||
// TODO: Run sorting tests per input, and testEquality.
|
||||
}
|
||||
|
||||
/**
|
||||
* Run sorting test with the given items (!). Inputs are copied to an ArrayList.
|
||||
*
|
||||
* @param items
|
||||
* @param fetcher
|
||||
*/
|
||||
private <F> void testSorting(List<F> items, IAccessSort<F> fetcher, int repeatShuffle) {
|
||||
// (1) Sorting can't be stable / result in the same in general.
|
||||
List<F> originalItems = items;
|
||||
items = new ArrayList<F>(items);
|
||||
LinkedList<F> sorted;
|
||||
for (int i = 0; i < 1 + repeatShuffle; i++) {
|
||||
sorted = fetcher.getSortedLinkedList(items);
|
||||
testIfContained(originalItems, sorted);
|
||||
testIfSorted(sorted, fetcher);
|
||||
//testEquality(sorted, fetcher, fetcher.getSortedLinkedList(originalItems), fetcher); // (1)
|
||||
Collections.reverse(items);
|
||||
// testEquality(sorted, fetcher, fetcher.getSortedLinkedList(items), fetcher); // (1)
|
||||
sorted = fetcher.getSortedLinkedList(items);
|
||||
testIfContained(originalItems, sorted);
|
||||
testIfSorted(sorted, fetcher);
|
||||
//testEquality(sorted, fetcher, fetcher.getSortedLinkedList(originalItems), fetcher); // (1)
|
||||
// Finally shuffle.
|
||||
shuffle2s(items);
|
||||
}
|
||||
}
|
||||
|
||||
private <F> void testIfSorted(List<F> items, IAccessSort<F> fetcher) {
|
||||
// Test priority ordering and rough region order.
|
||||
Integer lastPriority = null;
|
||||
Integer maxPriority = null;
|
||||
boolean priorityContained = false;
|
||||
boolean nullShouldFollow = false;
|
||||
for (final F item : items) {
|
||||
RegistrationOrder order = fetcher.getRegistrationOrder(item);
|
||||
Integer basePriority = order.getBasePriority();
|
||||
if (basePriority == null) {
|
||||
if (maxPriority == null) {
|
||||
if (order.getBeforeTag() == null && order.getAfterTag() != null) {
|
||||
nullShouldFollow = true;
|
||||
// Assume the pair-comparison would catch wrongly sorted null entries on occasion.
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (nullShouldFollow && order.getBeforeTag() != null) {
|
||||
fail("Invalid null-basePriority entry after basePriority>0 region: beforeTag is set.");
|
||||
}
|
||||
//
|
||||
if (maxPriority > 0) {
|
||||
nullShouldFollow = true;
|
||||
}
|
||||
else if (maxPriority == 0) {
|
||||
if (order.getBeforeTag() != null) {
|
||||
fail("Invalid null-basePriority entry within 0-basePriority region: beforeTag is set.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (nullShouldFollow) {
|
||||
fail("Invalid mixture of priority null/set after 0-basePriority region.");
|
||||
}
|
||||
else if (priorityContained && basePriority < 0 && lastPriority == null) {
|
||||
fail("Invalid mixture of priority null/set before 0-basePriority region.");
|
||||
}
|
||||
if (maxPriority == null) {
|
||||
maxPriority = basePriority;
|
||||
}
|
||||
else if (basePriority < maxPriority) {
|
||||
fail("Order by priority broken.");
|
||||
}
|
||||
else {
|
||||
maxPriority = basePriority;
|
||||
}
|
||||
priorityContained = true; // Remember to be able to exclude cases.
|
||||
}
|
||||
lastPriority = basePriority;
|
||||
}
|
||||
// Compare pairs, for being wrongly ordered.
|
||||
for (int i = 1; i <items.size(); i++) {
|
||||
// (Careful testing: greedy could apply both ways round.)
|
||||
if (!RegistrationOrder.AbstractRegistrationOrderSort.shouldSortBefore(
|
||||
fetcher.getRegistrationOrder(items.get(i - 1)),
|
||||
fetcher.getRegistrationOrder(items.get(i)))) {
|
||||
// Still test the other way round, to see if it's really demanded.
|
||||
if (RegistrationOrder.AbstractRegistrationOrderSort.shouldSortBefore(
|
||||
fetcher.getRegistrationOrder(items.get(i)),
|
||||
fetcher.getRegistrationOrder(items.get(i - 1)))) {
|
||||
// The following object is explicitly set to come before.
|
||||
fail("Pair is wrongly ordered.");
|
||||
}
|
||||
// TODO: Could still consider checking i-1 versus following items, until explicit stop/end.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <F> void testIfContained(List<F> originalItems, List<F> items) {
|
||||
if (!new HashSet<F>(items).containsAll(originalItems)) {
|
||||
fail("Sorted list does not contain all original items.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inputs are copied to an ArrayList.
|
||||
*
|
||||
* @param items1
|
||||
* @param fetcher1
|
||||
* @param items2
|
||||
* @param fetcher2
|
||||
*/
|
||||
private <F1, F2> void testEquality(List<F1> items1, IAccessSort<F1> fetcher1,
|
||||
List<F2> items2, IAccessSort<F2> fetcher2,
|
||||
int repeatShuffle) {
|
||||
// Test if sorting remains the same: original, and n times shuffled - input + reversed.
|
||||
items1 = new ArrayList<F1>(items1);
|
||||
items2 = new ArrayList<F2>(items2);
|
||||
List<F1> sorted1;
|
||||
List<F2> sorted2;
|
||||
for (int i = 0; i < repeatShuffle + 1; i++) {
|
||||
sorted1 = fetcher1.getSortedLinkedList(items1);
|
||||
sorted2 = fetcher2.getSortedLinkedList(items2);
|
||||
testEquality(sorted1, fetcher1, sorted2, fetcher2);
|
||||
Collections.reverse(items1);
|
||||
Collections.reverse(items2);
|
||||
sorted1 = fetcher1.getSortedLinkedList(items1);
|
||||
sorted2 = fetcher2.getSortedLinkedList(items2);
|
||||
testEquality(sorted1, fetcher1, sorted2, fetcher2);
|
||||
// In the end "shuffle" both inputs parallel.
|
||||
int[][] swap = getSwapIndices(items1.size(), items1.size() * 2);
|
||||
shuffle(items1, swap);
|
||||
shuffle(items2, swap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a somewhat regular randomized sample, having itemsPerLevel times
|
||||
* items per priority level plus, plus a similar amount of null entries.<br>
|
||||
* TODO: A version that guarantees every type is in?
|
||||
*
|
||||
* @param priorities
|
||||
* @param itemsPerLevel
|
||||
* @return
|
||||
*/
|
||||
private List<RegistrationOrder> getSample(int priorities, int itemsPerLevel) {
|
||||
ArrayList<RegistrationOrder> out = new ArrayList<RegistrationOrder>();
|
||||
int[] prioArr;
|
||||
if (priorities <= 0) {
|
||||
prioArr = new int[0];
|
||||
}
|
||||
else {
|
||||
Set<Integer> prios = new LinkedHashSet<Integer>();
|
||||
if (random.nextBoolean()) {
|
||||
prios.add(0);
|
||||
}
|
||||
while (prios.size() < priorities) {
|
||||
prios.add(random.nextInt(2 * priorities + 1) - priorities);
|
||||
}
|
||||
prioArr = new int[priorities];
|
||||
int index = 0;
|
||||
for (Integer x : prios) {
|
||||
prioArr[index] = x;
|
||||
index ++;
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < prioArr.length; i++) {
|
||||
for (int x = 0; x < itemsPerLevel; x++) {
|
||||
out.add(getRegistrationOrder(prioArr[i]));
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < itemsPerLevel; i++) {
|
||||
out.add(getRegistrationOrder(null));
|
||||
}
|
||||
shuffle2s(out); // Extra shuffle to potentially unlink.
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get one using the default tags, beforeTag and afterTag get set to null, 1 2 or 3 others.
|
||||
* @param priority
|
||||
* @return
|
||||
*/
|
||||
private RegistrationOrder getRegistrationOrder(Integer basePriority) {
|
||||
String tag = tags[random.nextInt(tags.length)];
|
||||
String afterTag = random.nextBoolean() ? null : getTagRegex(random.nextInt(3) + 1);
|
||||
String beforeTag = random.nextBoolean() ? null : getTagRegex(random.nextInt(3) + 1);
|
||||
// StaticLog.logInfo("RegistrationOrder(b " + basePriority + " t " + tag + " bt " + beforeTag + " at " + afterTag + ")");
|
||||
return new RegistrationOrder(basePriority, tag, beforeTag, afterTag);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a non null regex using default tags.
|
||||
* @param combinations
|
||||
*/
|
||||
private String getTagRegex(int combinations) {
|
||||
Set<String> indices = new HashSet<String>();
|
||||
while (indices.size() < combinations) {
|
||||
indices.add(tags[1 + random.nextInt(tags.length - 1)]); // Avoid null here.
|
||||
}
|
||||
// Combine tags to a regex (simple).
|
||||
return"(" + StringUtil.join(indices, "|") + ")";
|
||||
}
|
||||
|
||||
private <F1, F2> void testEquality(List<F1> items1, IAccessSort<F1> fetcher1,
|
||||
List<F2> items2, IAccessSort<F2> fetcher2) {
|
||||
for (int i = 0; i < items1.size(); i++) {
|
||||
if (!fetcher1.getRegistrationOrder(items1.get(i)).equals(fetcher2.getRegistrationOrder(items2.get(i)))) {
|
||||
// TODO: Return / log outside to give context.
|
||||
doFail("Lists are not the same");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle by swapping items according to the give indices.
|
||||
*
|
||||
* @param items
|
||||
* @param swap
|
||||
* int[N][2], containing valid indices for items.
|
||||
*/
|
||||
private <F> void shuffle(List<F> items, int[][] swap) {
|
||||
for (int x = 0; x < swap.length; x++) {
|
||||
F temp1 = items.get(swap[x][1]);
|
||||
items.set(swap[x][1], items.get(swap[x][0]));
|
||||
items.set(swap[x][0], temp1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuffle by swapping 2 times the list size items.
|
||||
*
|
||||
* @param items
|
||||
*/
|
||||
private <F> void shuffle2s(List<F> items) {
|
||||
shuffle(items, 2 * items.size());
|
||||
}
|
||||
|
||||
private <F> void shuffle(List<F> items, int n) {
|
||||
int[][] swap = getSwapIndices(items.size(), n);
|
||||
for (int x = 0; x < swap.length; x++) {
|
||||
F temp1 = items.get(swap[x][1]);
|
||||
items.set(swap[x][1], items.get(swap[x][0]));
|
||||
items.set(swap[x][0], temp1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for "shuffling".
|
||||
* @param upperBound Excluded.
|
||||
* @param n
|
||||
* @return
|
||||
*/
|
||||
private int[][] getSwapIndices(int upperBound, int n) {
|
||||
int[][] out = new int[n][2];
|
||||
for (int i = 0; i < n; i++) {
|
||||
out[i][0] = random.nextInt(upperBound);
|
||||
out[i][1] = random.nextInt(upperBound);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private void doFail(String message) {
|
||||
// TODO: Args.
|
||||
fail(message);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user