Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/main/java/ch/njol/skript/lang/ExpressionInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import org.jetbrains.annotations.Nullable;

import java.util.function.Supplier;

/**
* Represents an expression's information, for use when creating new instances of expressions.
*/
Expand All @@ -10,12 +12,24 @@ public class ExpressionInfo<E extends Expression<T>, T> extends SyntaxElementInf
public @Nullable ExpressionType expressionType;
public Class<T> returnType;

public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath) throws IllegalArgumentException {
this(patterns, returnType, expressionClass, originClassPath, null);
public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath)
throws IllegalArgumentException {
this(patterns, returnType, expressionClass, originClassPath, (Supplier<E>) null);
}

public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath,
@Nullable Supplier<E> supplier) throws IllegalArgumentException {
this(patterns, returnType, expressionClass, originClassPath, null, supplier);
}

public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath,
@Nullable ExpressionType expressionType) {
this(patterns, returnType, expressionClass, originClassPath, expressionType, null);
}

public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath, @Nullable ExpressionType expressionType) throws IllegalArgumentException {
super(patterns, expressionClass, originClassPath);
public ExpressionInfo(String[] patterns, Class<T> returnType, Class<E> expressionClass, String originClassPath,
@Nullable ExpressionType expressionType, @Nullable Supplier<E> supplier) throws IllegalArgumentException {
super(patterns, expressionClass, originClassPath, supplier);
this.returnType = returnType;
this.expressionType = expressionType;
}
Expand Down
12 changes: 10 additions & 2 deletions src/main/java/ch/njol/skript/lang/SkriptEventInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.function.Supplier;

public sealed class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo<E> permits ModernSkriptEventInfo {

Expand All @@ -37,15 +38,22 @@ public sealed class SkriptEventInfo<E extends SkriptEvent> extends StructureInfo

private final String id;

public SkriptEventInfo(String name, String[] patterns, Class<E> eventClass, String originClassPath,
Class<? extends Event>[] events) {
this(name, patterns, eventClass, originClassPath, events, null);
}

/**
* @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this).
* @param patterns The Skript patterns to use for this event
* @param eventClass The SkriptEvent's class
* @param originClassPath The class path for the origin of this event.
* @param events The Bukkit-Events this SkriptEvent listens to
* @param supplier supplier of the syntax info instance
*/
public SkriptEventInfo(String name, String[] patterns, Class<E> eventClass, String originClassPath, Class<? extends Event>[] events) {
super(patterns, eventClass, originClassPath);
public SkriptEventInfo(String name, String[] patterns, Class<E> eventClass, String originClassPath,
Class<? extends Event>[] events, @Nullable Supplier<E> supplier) {
super(patterns, eventClass, originClassPath, supplier);
for (int i = 0; i < events.length; i++) {
for (int j = i + 1; j < events.length; j++) {
if (events[i].isAssignableFrom(events[j]) || events[j].isAssignableFrom(events[i])) {
Expand Down
57 changes: 32 additions & 25 deletions src/main/java/ch/njol/skript/lang/SyntaxElementInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,22 @@
import org.bukkit.event.Event;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Unmodifiable;
import org.skriptlang.skript.bukkit.registration.BukkitSyntaxInfos;
import org.skriptlang.skript.registration.SyntaxInfo;
import org.skriptlang.skript.lang.structure.StructureInfo;

import ch.njol.skript.SkriptAPIException;
import org.skriptlang.skript.registration.SyntaxOrigin;
import org.skriptlang.skript.util.ClassUtils;
import org.skriptlang.skript.util.Priority;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.function.Supplier;

/**
* @param <E> the syntax element this info is for
Expand All @@ -27,22 +29,20 @@ public class SyntaxElementInfo<E extends SyntaxElement> implements SyntaxInfo<E>
public final Class<E> elementClass;
public final String[] patterns;
public final String originClassPath;
private Supplier<E> instanceSupplier;

public SyntaxElementInfo(String[] patterns, Class<E> elementClass, String originClassPath) throws IllegalArgumentException {
public SyntaxElementInfo(String[] patterns, Class<E> elementClass, String originClassPath) {
this(patterns, elementClass, originClassPath, null);
}

public SyntaxElementInfo(String[] patterns, Class<E> elementClass, String originClassPath,
@Nullable Supplier<E> instanceSupplier) throws IllegalArgumentException {
if (Modifier.isAbstract(elementClass.getModifiers()))
throw new SkriptAPIException("Class " + elementClass.getName() + " is abstract");

this.patterns = patterns;
this.elementClass = elementClass;
this.originClassPath = originClassPath;
try {
elementClass.getConstructor();
} catch (final NoSuchMethodException e) {
// throwing an Exception throws an (empty) ExceptionInInitializerError instead, thus an Error is used
throw new Error(elementClass + " does not have a public nullary constructor", e);
} catch (final SecurityException e) {
throw new IllegalStateException("Skript cannot run properly because a security manager is blocking it!");
}
this.instanceSupplier = instanceSupplier;
}

/**
Expand Down Expand Up @@ -76,15 +76,16 @@ public String getOriginClassPath() {
public static <I extends SyntaxElementInfo<E>, E extends SyntaxElement> I fromModern(SyntaxInfo<? extends E> info) {
if (info instanceof SyntaxElementInfo<? extends E> oldInfo) {
return (I) oldInfo;
} else if (info instanceof BukkitSyntaxInfos.Event<?> event) {
} else if (info instanceof BukkitSyntaxInfos.Event<?>) {
BukkitSyntaxInfos.Event<SkriptEvent> event = (BukkitSyntaxInfos.Event<SkriptEvent>) info;
// We must first go back to the raw input
String rawName = event.name().startsWith("On ")
? event.name().substring(3)
: "*" + event.name();
SkriptEventInfo<?> eventInfo = new SkriptEventInfo<>(
rawName, event.patterns().toArray(new String[0]),
event.type(), event.origin().name(),
(Class<? extends Event>[]) event.events().toArray(new Class<?>[0]));
(Class<? extends Event>[]) event.events().toArray(new Class<?>[0]), event::instance);
String documentationId = event.documentationId();
if (documentationId != null)
eventInfo.documentationID(documentationId);
Expand All @@ -94,24 +95,28 @@ public static <I extends SyntaxElementInfo<E>, E extends SyntaxElement> I fromMo
.examples(event.examples().toArray(new String[0]))
.keywords(event.keywords().toArray(new String[0]))
.requiredPlugins(event.requiredPlugins().toArray(new String[0]));

return (I) eventInfo;
} else if (info instanceof SyntaxInfo.Structure<?> structure) {
} else if (info instanceof SyntaxInfo.Structure<?>) {
var structure = (Structure<org.skriptlang.skript.lang.structure.Structure>) info;
return (I) new StructureInfo<>(structure.patterns().toArray(new String[0]), structure.type(),
structure.origin().name(), structure.entryValidator(), structure.nodeType());
structure.origin().name(), structure.entryValidator(), structure.nodeType(),
structure::instance);
} else if (info instanceof SyntaxInfo.Expression<?, ?> expression) {
return (I) fromModernExpression(expression);
}

return (I) new SyntaxElementInfo<>(info.patterns().toArray(new String[0]), info.type(), info.origin().name());
return (I) new SyntaxElementInfo<>(info.patterns().toArray(new String[0]), (Class<E>) info.type(), info.origin().name(),
info::instance);
}

@Contract("_ -> new")
@ApiStatus.Experimental
private static <E extends ch.njol.skript.lang.Expression<R>, R> ExpressionInfo<E, R> fromModernExpression(SyntaxInfo.Expression<E, R> info) {
private static <E extends ch.njol.skript.lang.Expression<R>, R> ExpressionInfo<E, R> fromModernExpression(
SyntaxInfo.Expression<E, R> info) {
return new ExpressionInfo<>(
info.patterns().toArray(new String[0]), info.returnType(),
info.type(), info.origin().name(), ExpressionType.fromModern(info.priority())
info.type(), info.origin().name(), ExpressionType.fromModern(info.priority()),
info::instance
);
}

Expand Down Expand Up @@ -139,12 +144,14 @@ public Class<E> type() {
@Override
@ApiStatus.Internal
public E instance() {
try {
return type().getDeclaredConstructor().newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
throw new RuntimeException(e);
if (instanceSupplier == null) {
try {
instanceSupplier = ClassUtils.instanceSupplier(getElementClass());
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
return instanceSupplier.get();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.skriptlang.skript.lang.entry.EntryValidator;
import org.skriptlang.skript.registration.SyntaxInfo;

import java.util.function.Supplier;

/**
* Special {@link SyntaxElementInfo} for {@link Structure}s that may contain information such as the {@link EntryValidator}.
*/
Expand All @@ -23,21 +25,42 @@ public class StructureInfo<E extends Structure> extends SyntaxElementInfo<E> {
public final SyntaxInfo.Structure.NodeType nodeType;

public StructureInfo(String[] patterns, Class<E> c, String originClassPath) throws IllegalArgumentException {
this(patterns, c, originClassPath, false);
this(patterns, c, originClassPath, (Supplier<E>) null);
}

public StructureInfo(String[] patterns, Class<E> c, String originClassPath,
@Nullable Supplier<E> supplier) throws IllegalArgumentException {
this(patterns, c, originClassPath, false, supplier);
}

public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath, boolean simple)
throws IllegalArgumentException {
this(patterns, elementClass, originClassPath, simple, null);
}

public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath, boolean simple) throws IllegalArgumentException {
this(patterns, elementClass, originClassPath, null, simple ? SyntaxInfo.Structure.NodeType.SIMPLE : SyntaxInfo.Structure.NodeType.SECTION);
public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath, boolean simple,
@Nullable Supplier<E> supplier) throws IllegalArgumentException {
this(patterns, elementClass, originClassPath, null,
simple ? SyntaxInfo.Structure.NodeType.SIMPLE : SyntaxInfo.Structure.NodeType.SECTION, supplier);
}

public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath, @Nullable EntryValidator entryValidator) throws IllegalArgumentException {
public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath,
@Nullable EntryValidator entryValidator) throws IllegalArgumentException {
this(patterns, elementClass, originClassPath, entryValidator, SyntaxInfo.Structure.NodeType.SECTION);
}

@ApiStatus.Experimental
public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath,
@Nullable EntryValidator entryValidator, SyntaxInfo.Structure.NodeType nodeType) throws IllegalArgumentException {
super(patterns, elementClass, originClassPath);
@Nullable EntryValidator entryValidator, SyntaxInfo.Structure.NodeType nodeType)
throws IllegalArgumentException {
this(patterns, elementClass, originClassPath, entryValidator, nodeType, null);
}

@ApiStatus.Experimental
public StructureInfo(String[] patterns, Class<E> elementClass, String originClassPath,
@Nullable EntryValidator entryValidator, SyntaxInfo.Structure.NodeType nodeType,
@Nullable Supplier<E> supplier) {
super(patterns, elementClass, originClassPath, supplier);
this.entryValidator = entryValidator;
this.nodeType = nodeType;
this.simple = nodeType.canBeSimple();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class SyntaxInfoImpl<T extends SyntaxElement> implements SyntaxInfo<T> {

private final SyntaxOrigin origin;
private final Class<T> type;
private final @Nullable Supplier<T> supplier;
private @Nullable Supplier<T> supplier;
private final boolean providedSupplier;
private final Collection<String> patterns;
private final Priority priority;

Expand All @@ -37,6 +38,7 @@ protected SyntaxInfoImpl(
this.origin = origin;
this.type = type;
this.supplier = supplier;
providedSupplier = supplier != null;
this.patterns = ImmutableList.copyOf(patterns);
this.priority = priority;
}
Expand All @@ -45,7 +47,7 @@ protected SyntaxInfoImpl(
public Builder<? extends Builder<?, T>, T> toBuilder() {
var builder = new BuilderImpl<>(type);
builder.origin(origin);
if (supplier != null) {
if (providedSupplier) {
builder.supplier(supplier);
}
builder.addPatterns(patterns);
Expand All @@ -65,11 +67,14 @@ public Class<T> type() {

@Override
public T instance() {
try {
return supplier == null ? type.getDeclaredConstructor().newInstance() : supplier.get();
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
if (supplier == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be fine to set the supplier when the object is constructed so that the field can continue to be final.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think creating a lot of metafactories can get fairly expensive so I wanted to initialize them lazily. I'll check the performance cost during startup and see how bad it gets.

try {
supplier = ClassUtils.instanceSupplier(type);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
return supplier.get();
}

@Override
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/org/skriptlang/skript/util/ClassUtils.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
package org.skriptlang.skript.util;

import com.google.common.base.Preconditions;

import java.lang.invoke.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

/**
* Utilities for interacting with classes.
Expand All @@ -16,4 +23,36 @@ public static boolean isNormalClass(Class<?> clazz) {
&& !clazz.isInterface() && !Modifier.isAbstract(clazz.getModifiers());
}

private static final Map<Class<?>, Supplier<?>> instanceSuppliers = new ConcurrentHashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think a cache is necessary? The method would probably only be called once per registered syntax, right?


/**
* Creates a supplier for the given class if its nullary constructor exists.
*
* @param type class to create the supplier for
* @return supplier for the instances of given class, using its nullary constructor
* @param <T> type
*/
@SuppressWarnings("unchecked")
public static <T> Supplier<T> instanceSupplier(Class<T> type) throws Throwable {
Supplier<?> cached = instanceSuppliers.get(type);
if (cached != null)
return (Supplier<T>) cached;
Preconditions.checkArgument(
!Modifier.isAbstract(type.getModifiers()) && !Modifier.isInterface(type.getModifiers()),
"You cannot create instance suppliers for abstract classes");
Constructor<T> nullaryConstructor = type.getDeclaredConstructor();
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(type, MethodHandles.lookup());
MethodHandle methodHandle = lookup.unreflectConstructor(nullaryConstructor);
CallSite callSite = LambdaMetafactory.metafactory(lookup,
"get",
MethodType.methodType(Supplier.class),
MethodType.methodType(Object.class),
methodHandle,
methodHandle.type()
);
Supplier<T> created = (Supplier<T>) callSite.getTarget().invokeExact();
instanceSuppliers.put(type, created);
return created;
}

}