Skip to content

Commit

Permalink
Converter and Comparator improvements (SkriptLang#5403)
Browse files Browse the repository at this point in the history
  • Loading branch information
APickledWalrus authored Mar 10, 2023
1 parent 2fcb4d4 commit 9291132
Show file tree
Hide file tree
Showing 9 changed files with 308 additions and 185 deletions.
4 changes: 2 additions & 2 deletions src/main/java/ch/njol/skript/Skript.java
Original file line number Diff line number Diff line change
Expand Up @@ -1263,9 +1263,9 @@ public static void checkAcceptRegistrations() {
}

private static void stopAcceptingRegistrations() {
acceptRegistrations = false;

Converters.createChainedConverters();

acceptRegistrations = false;

Classes.onRegistrationsStop();
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ch/njol/skript/registrations/Classes.java
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ public static <T> T parse(final String s, final Class<T> c, final ParseContext c
log.printLog();
return t;
}
for (final ConverterInfo<?, ?> conv : org.skriptlang.skript.lang.converter.Converters.getConverterInfo()) {
for (final ConverterInfo<?, ?> conv : Converters.getConverterInfos()) {
if (context == ParseContext.COMMAND && (conv.getFlags() & Commands.CONVERTER_NO_COMMAND_ARGUMENTS) != 0)
continue;
if (c.isAssignableFrom(conv.getTo())) {
Expand Down Expand Up @@ -539,7 +539,7 @@ public static <T> Parser<? extends T> getParser(final Class<T> to) {
if (to.isAssignableFrom(ci.getC()) && ci.getParser() != null)
return (Parser<? extends T>) ci.getParser();
}
for (final ConverterInfo<?, ?> conv : org.skriptlang.skript.lang.converter.Converters.getConverterInfo()) {
for (final ConverterInfo<?, ?> conv : Converters.getConverterInfos()) {
if (to.isAssignableFrom(conv.getTo())) {
for (int i = classInfos.length - 1; i >= 0; i--) {
final ClassInfo<?> ci = classInfos[i];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public static Relation compare(final @Nullable Object o1, final @Nullable Object
}

public static java.util.Comparator<Object> getJavaComparator() {
return org.skriptlang.skript.lang.comparator.Comparators.JAVA_COMPARATOR;
return (o1, o2) -> compare(o1, o2).getRelation();
}

@Nullable
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/ch/njol/skript/registrations/Converters.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ private Converters() {}

@SuppressWarnings("unchecked")
public static <F, T> List<ConverterInfo<?, ?>> getConverters() {
return org.skriptlang.skript.lang.converter.Converters.getConverterInfo().stream()
return org.skriptlang.skript.lang.converter.Converters.getConverterInfos().stream()
.map(unknownInfo -> {
org.skriptlang.skript.lang.converter.ConverterInfo<F, T> info = (org.skriptlang.skript.lang.converter.ConverterInfo<F, T>) unknownInfo;
return new ConverterInfo<>(info.getFrom(), info.getTo(), info.getConverter()::convert, info.getFlags());
Expand Down
196 changes: 132 additions & 64 deletions src/main/java/org/skriptlang/skript/lang/comparator/Comparators.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@

import ch.njol.skript.Skript;
import ch.njol.skript.SkriptAPIException;
import ch.njol.skript.classes.Converter;
import ch.njol.skript.registrations.Converters;
import ch.njol.util.Pair;
import org.eclipse.jdt.annotation.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.skriptlang.skript.lang.converter.Converter;
import org.skriptlang.skript.lang.converter.Converters;

import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -44,35 +45,35 @@ private Comparators() {}
/**
* A default comparator to compare two objects using {@link Object#equals(Object)}.
*/
public static final Comparator<Object, Object> EQUALS_COMPARATOR = (o1, o2) -> Relation.get(o1.equals(o2));

/**
* A Java {@link java.util.Comparator} for comparing two objects using {@link #compare(Object, Object)}.
*/
public static final java.util.Comparator<Object> JAVA_COMPARATOR = (o1, o2) -> compare(o1, o2).getRelation();
private static final ComparatorInfo<Object, Object> EQUALS_COMPARATOR_INFO = new ComparatorInfo<>(
Object.class,
Object.class,
(o1, o2) -> Relation.get(o1.equals(o2))
);

/**
* A List containing information for all registered comparators.
*/
private static final List<ComparatorInfo<?, ?>> COMPARATORS = Collections.synchronizedList(new ArrayList<>());
private static final List<ComparatorInfo<?, ?>> COMPARATORS = new ArrayList<>(50);

/**
* @return An unmodifiable, synchronized list containing all registered {@link ComparatorInfo}s.
* When traversing this list, please refer to {@link Collections#synchronizedList(List)} to ensure that
* the list is properly traversed due to its synchronized status.
* @return An unmodifiable list containing all registered {@link ComparatorInfo}s.
* Please note that this does not include any special Comparators resolved by Skript during runtime.
* This method ONLY returns Comparators explicitly registered during registration.
* Thus, it is recommended to use {@link #getComparator(Class, Class)} if possible.
*/
@Unmodifiable
public static List<ComparatorInfo<?, ?>> getComparatorInfos() {
assertIsDoneLoading();
return Collections.unmodifiableList(COMPARATORS);
}

/**
* A map for quickly accessing comparators that have already been resolved.
* Some pairs may point to a null value, indicating that no comparator exists between the two types.
* This is useful for skipping complex lookups that may require conversion and inversion.
*/
private static final Map<Pair<Class<?>, Class<?>>, Comparator<?, ?>> QUICK_ACCESS_COMPARATORS = Collections.synchronizedMap(new HashMap<>());
private static final Map<Pair<Class<?>, Class<?>>, ComparatorInfo<?, ?>> QUICK_ACCESS_COMPARATORS = new HashMap<>(50);

/**
* Registers a new Comparator with Skript's collection of Comparators.
Expand All @@ -87,60 +88,86 @@ public static <T1, T2> void registerComparator(
) {
Skript.checkAcceptRegistrations();

if (firstType == Object.class && secondType == Object.class)
if (firstType == Object.class && secondType == Object.class) {
throw new IllegalArgumentException("It is not possible to add a comparator between objects");
}

for (ComparatorInfo<?, ?> info : COMPARATORS) {
if (info.firstType == firstType && info.secondType == secondType) {
throw new SkriptAPIException(
"A Comparator comparing '" + firstType + "' and '" + secondType + " already exists!"
);
synchronized (COMPARATORS) {
for (ComparatorInfo<?, ?> info : COMPARATORS) {
if (info.firstType == firstType && info.secondType == secondType) {
throw new SkriptAPIException(
"A Comparator comparing '" + firstType + "' and '" + secondType + "' already exists!"
);
}
}
COMPARATORS.add(new ComparatorInfo<>(firstType, secondType, comparator));
}

COMPARATORS.add(new ComparatorInfo<>(firstType, secondType, comparator));
}

/**
* Compares two objects to see if a Relation exists between them.
* @param first The first object for comparison.
* @param second The second object for comparison.
* @return The Relation between the two provided objects.
* Guaranteed to be {@link Relation#NOT_EQUAL} if either parameter is null.
*/
@SuppressWarnings("unchecked")
public static <T1, T2> Relation compare(@Nullable T1 first, @Nullable T2 second) {
if (first == null || second == null)
assertIsDoneLoading(); // this would be checked later on too, but we want this guaranteed to fail

if (first == null || second == null) {
return Relation.NOT_EQUAL;
}

if (first == second) { // easiest check of them all!
return Relation.EQUAL;
}

Comparator<T1, T2> comparator = getComparator((Class<T1>) first.getClass(), (Class<T2>) second.getClass());
if (comparator == null)
if (comparator == null) {
return Relation.NOT_EQUAL;
}

return comparator.compare(first, second);
}

/**
* A method for obtaining a comparator that can compare two objects of 'firstType' and 'secondType'.
* A method for obtaining a Comparator that can compare two objects of 'firstType' and 'secondType'.
* Please note that comparators may convert objects if necessary for comparisons.
* @param firstType The first type for comparison.
* @param secondType The second type for comparison.
* @return A comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'.
* @return A Comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'.
* Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found.
*/
@Nullable
@SuppressWarnings("unchecked")
public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Class<T2> secondType) {
if (Skript.isAcceptRegistrations())
throw new SkriptAPIException("Comparators cannot be retrieved until Skript has finished registrations.");
ComparatorInfo<T1, T2> info = getComparatorInfo(firstType, secondType);
return info != null ? info.comparator : null;
}

Pair<Class<?>, Class<?>> pair = new Pair<>(firstType, secondType);
Comparator<T1, T2> comparator;
/**
* A method for obtaining the info of a Comparator that can compare two objects of 'firstType' and 'secondType'.
* Please note that comparators may convert objects if necessary for comparisons.
* @param firstType The first type for comparison.
* @param secondType The second type for comparison.
* @return The info of a Comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'.
* Will be null if no info for comparing two objects of 'firstType' and 'secondType' was found.
*/
@Nullable
@SuppressWarnings("unchecked")
public static <T1, T2> ComparatorInfo<T1, T2> getComparatorInfo(Class<T1> firstType, Class<T2> secondType) {
assertIsDoneLoading();

if (QUICK_ACCESS_COMPARATORS.containsKey(pair)) {
comparator = (Comparator<T1, T2>) QUICK_ACCESS_COMPARATORS.get(pair);
} else { // Compute QUICK_ACCESS for provided types
comparator = getComparator_i(firstType, secondType);
QUICK_ACCESS_COMPARATORS.put(pair, comparator);
Pair<Class<?>, Class<?>> pair = new Pair<>(firstType, secondType);
ComparatorInfo<T1, T2> comparator;

synchronized (QUICK_ACCESS_COMPARATORS) {
if (QUICK_ACCESS_COMPARATORS.containsKey(pair)) {
comparator = (ComparatorInfo<T1, T2>) QUICK_ACCESS_COMPARATORS.get(pair);
} else { // Compute QUICK_ACCESS for provided types
comparator = getComparatorInfo_i(firstType, secondType);
QUICK_ACCESS_COMPARATORS.put(pair, comparator);
}
}

return comparator;
Expand All @@ -151,7 +178,7 @@ public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Cla
* This method handles regular {@link Comparator}s, {@link ConvertedComparator}s, and {@link InverseComparator}s.
* @param firstType The first type for comparison.
* @param secondType The second type for comparison.
* @return A comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'.
* @return The info of the comparator capable of determine the {@link Relation} between two objects of 'firstType' and 'secondType'.
* Will be null if no comparator capable of comparing two objects of 'firstType' and 'secondType' was found.
* @param <T1> The first type for comparison.
* @param <T2> The second type for comparison.
Expand All @@ -162,29 +189,32 @@ public static <T1, T2> Comparator<T1, T2> getComparator(Class<T1> firstType, Cla
*/
@Nullable
@SuppressWarnings("unchecked")
private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i(
private static <T1, T2, C1, C2> ComparatorInfo<T1, T2> getComparatorInfo_i(
Class<T1> firstType,
Class<T2> secondType
) {

// Look for an exact match
for (ComparatorInfo<?, ?> info : COMPARATORS) {
if (info.firstType == firstType && info.secondType == secondType) {
return (Comparator<T1, T2>) info.comparator;
return (ComparatorInfo<T1, T2>) info;
}
}

// Look for a basically perfect match
for (ComparatorInfo<?, ?> info : COMPARATORS) {
if (info.firstType.isAssignableFrom(firstType) && info.secondType.isAssignableFrom(secondType)) {
return (Comparator<T1, T2>) info.comparator;
return (ComparatorInfo<T1, T2>) info;
}
}

// Try to match and create an InverseComparator
for (ComparatorInfo<?, ?> info : COMPARATORS) {
if (info.comparator.supportsInversion() && info.firstType.isAssignableFrom(secondType) && info.secondType.isAssignableFrom(firstType)) {
return new InverseComparator<>((Comparator<T2, T1>) info.comparator);
return new ComparatorInfo<>(
firstType,
secondType,
new InverseComparator<>((Comparator<T2, T1>) info.comparator)
);
}
}

Expand All @@ -193,36 +223,57 @@ private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i(
ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo;

if (info.firstType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the second comparator type
Converter<T2, C2> sc2 = (Converter<T2, C2>) Converters.getConverter(secondType, info.secondType);
if (sc2 != null)
return new ConvertedComparator<>(null, info.comparator, sc2);
Converter<T2, C2> sc2 = Converters.getConverter(secondType, info.secondType);
if (sc2 != null) {
return new ComparatorInfo<>(
firstType,
secondType,
new ConvertedComparator<>(null, info.comparator, sc2)
);
}
}

if (info.secondType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the first comparator type
Converter<T1, C1> fc1 = (Converter<T1, C1>) Converters.getConverter(firstType, info.firstType);
if (fc1 != null)
return new ConvertedComparator<>(fc1, info.comparator, null);
Converter<T1, C1> fc1 = Converters.getConverter(firstType, info.firstType);
if (fc1 != null) {
return new ComparatorInfo<>(
firstType,
secondType,
new ConvertedComparator<>(fc1, info.comparator, null)
);
}
}

}

// Attempt converting one parameter but with reversed types
for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) {
if (!unknownInfo.comparator.supportsInversion()) // Unsupported for reversing types
if (!unknownInfo.comparator.supportsInversion()) { // Unsupported for reversing types
continue;
}

ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo;

if (info.secondType.isAssignableFrom(firstType)) { // Attempt to convert the second argument to the first comparator type
Converter<T2, C1> sc1 = (Converter<T2, C1>) Converters.getConverter(secondType, info.firstType);
if (sc1 != null)
return new InverseComparator<>(new ConvertedComparator<>(sc1, info.comparator, null));
Converter<T2, C1> sc1 = Converters.getConverter(secondType, info.firstType);
if (sc1 != null) {
return new ComparatorInfo<>(
firstType,
secondType,
new InverseComparator<>(new ConvertedComparator<>(sc1, info.comparator, null))
);
}
}

if (info.firstType.isAssignableFrom(secondType)) { // Attempt to convert the first argument to the second comparator type
Converter<T1, C2> fc2 = (Converter<T1, C2>) Converters.getConverter(firstType, info.secondType);
if (fc2 != null)
new InverseComparator<>(new ConvertedComparator<>(null, info.comparator, fc2));
Converter<T1, C2> fc2 = Converters.getConverter(firstType, info.secondType);
if (fc2 != null) {
return new ComparatorInfo<>(
firstType,
secondType,
new InverseComparator<>(new ConvertedComparator<>(null, info.comparator, fc2))
);
}
}

}
Expand All @@ -231,34 +282,51 @@ private static <T1, T2, C1, C2> Comparator<T1, T2> getComparator_i(
for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) {
ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo;

Converter<T1, C1> c1 = (Converter<T1, C1>) Converters.getConverter(firstType, info.firstType);
Converter<T2, C2> c2 = (Converter<T2, C2>) Converters.getConverter(secondType, info.secondType);
if (c1 != null && c2 != null)
return new ConvertedComparator<>(c1, info.comparator, c2);
Converter<T1, C1> c1 = Converters.getConverter(firstType, info.firstType);
Converter<T2, C2> c2 = Converters.getConverter(secondType, info.secondType);
if (c1 != null && c2 != null) {
return new ComparatorInfo<>(
firstType,
secondType,
new ConvertedComparator<>(c1, info.comparator, c2)
);
}

}

// Attempt converting both parameters but with reversed types
for (ComparatorInfo<?, ?> unknownInfo : COMPARATORS) {
if (!unknownInfo.comparator.supportsInversion()) // Unsupported for reversing types
if (!unknownInfo.comparator.supportsInversion()) { // Unsupported for reversing types
continue;
}

ComparatorInfo<C1, C2> info = (ComparatorInfo<C1, C2>) unknownInfo;

Converter<T1, C2> c1 = (Converter<T1, C2>) Converters.getConverter(firstType, info.secondType);
Converter<T2, C1> c2 = (Converter<T2, C1>) Converters.getConverter(secondType, info.firstType);
if (c1 != null && c2 != null)
return new InverseComparator<>(new ConvertedComparator<>(c2, info.comparator, c1));
Converter<T1, C2> c1 = Converters.getConverter(firstType, info.secondType);
Converter<T2, C1> c2 = Converters.getConverter(secondType, info.firstType);
if (c1 != null && c2 != null) {
return new ComparatorInfo<>(
firstType,
secondType,
new InverseComparator<>(new ConvertedComparator<>(c2, info.comparator, c1))
);
}

}

// Same class but no comparator
if (firstType != Object.class && secondType == firstType) {
return (Comparator<T1, T2>) EQUALS_COMPARATOR;
return (ComparatorInfo<T1, T2>) EQUALS_COMPARATOR_INFO;
}

// Well, we tried!
return null;
}

private static void assertIsDoneLoading() {
if (Skript.isAcceptRegistrations()) {
throw new SkriptAPIException("Comparators cannot be retrieved until Skript has finished registrations.");
}
}

}
Loading

0 comments on commit 9291132

Please sign in to comment.