Skip to content

Commit

Permalink
GH-531 Use generated injectors to invoke parser methods with a native…
Browse files Browse the repository at this point in the history
… performance
  • Loading branch information
dzikoysk committed Jul 7, 2020
1 parent 309e521 commit 0b9ae3b
Show file tree
Hide file tree
Showing 38 changed files with 147 additions and 104 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.panda_lang.utilities.commons;

import org.jetbrains.annotations.Nullable;
import org.panda_lang.utilities.commons.collection.Pair;
import org.panda_lang.utilities.commons.function.Option;
import org.panda_lang.utilities.commons.function.ThrowingConsumer;

Expand Down Expand Up @@ -218,15 +219,26 @@ public static Class<?> getDimensionalArrayType(Class<?> type, int dimensions) {
* @param arrayClass the array class
* @return the base type
*/
@SuppressWarnings("IdempotentLoopBody")
public static Class<?> getBaseClass(Class<?> arrayClass) {
return getBaseClassWithDimensions(arrayClass).getKey();
}

/**
* Get base class of any array, e.g. Integer from Integer[][][][][]
*
* @param arrayClass the array class
* @return the base type
*/
public static Pair<Class<?>, Integer> getBaseClassWithDimensions(Class<?> arrayClass) {
Class<?> currentClass = arrayClass;
int dimensions = 0;

while (currentClass.isArray()) {
currentClass = arrayClass.getComponentType();
dimensions++;
}

return currentClass;
return new Pair<>(currentClass, dimensions);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@

package org.panda_lang.utilities.inject;

import org.panda_lang.utilities.commons.ArrayUtils;
import org.panda_lang.utilities.commons.ObjectUtils;
import org.panda_lang.utilities.commons.StringUtils;
import org.panda_lang.utilities.commons.collection.Pair;
import org.panda_lang.utilities.commons.javassist.implementer.FunctionalInterfaceImplementer;

import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;

final class GeneratedMethodInjector {
public final class GeneratedMethodInjector {

private static final AtomicInteger ID = new AtomicInteger();
private static final FunctionalInterfaceImplementer FUNCTIONAL_INTERFACE_IMPLEMENTER = new FunctionalInterfaceImplementer();
Expand All @@ -44,28 +47,48 @@ final class GeneratedMethodInjector {
}

@SuppressWarnings("unchecked")
public <T> T invoke(Object instance) throws Throwable {
return (T) function.apply(instance, empty ? InjectorProcessor.EMPTY : processor.fetchValues(cache, method));
public <T> T invoke(Object instance, Object... injectorArgs) throws Throwable {
return (T) function.apply(instance, empty ? InjectorProcessor.EMPTY : processor.fetchValues(cache, method, injectorArgs));
}

private static BiFunction<Object, Object[], Object> generate(Method method) throws Exception {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> declaringClass = method.getDeclaringClass();
boolean isVoid = method.getReturnType() == void.class;

StringBuilder body = new StringBuilder("").append(declaringClass.getName()).append(" instance = (").append(declaringClass.getName()).append(") $1;");
body.append("return instance.").append(method.getName()).append("(");
StringBuilder body = new StringBuilder("");
body.append(declaringClass.getName()).append(" instance = (").append(declaringClass.getName()).append(") $1;").append(System.lineSeparator());
body.append(Object.class.getName()).append("[] array = (").append(Object.class.getName()).append("[]) $2;");

if (!isVoid) {
body.append("return ");
}

body.append("instance.").append(method.getName()).append("(");

for (int index = 0; index < parameterTypes.length; index++) {
body.append("(").append(parameterTypes[index].getName()).append(") $2[").append(index).append("], ");
Class<?> parameterType = parameterTypes[index];
String type = parameterType.getName();

if (parameterType.isArray()) {
Pair<Class<?>, Integer> baseClass = ArrayUtils.getBaseClassWithDimensions(parameterType);
type = baseClass.getKey().getName() + StringUtils.repeated(baseClass.getValue(), "[]");
}

body.append("(").append(type).append(") array[").append(index).append("], ").append(System.lineSeparator());
}

if (parameterTypes.length > 0) {
body.setLength(body.length() - 2);
body.setLength(body.length() - (", " + System.lineSeparator()).length());
}

body.append(");");

String name = "PandaDI" + method.getDeclaringClass().getSimpleName() + method.getName();
if (isVoid) {
body.append("return null;");
}

String name = "PandaDI$" + ID.incrementAndGet() + "$" + method.getDeclaringClass().getSimpleName() + method.getName();
Class<?> type = FUNCTIONAL_INTERFACE_IMPLEMENTER.generate(name, BiFunction.class, new LinkedHashMap<>(), body);

return ObjectUtils.cast(type.newInstance());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public interface Injector {
* @param parameter the parameter invoke
* @param <T> type of expected value
* @return the associated binding value
* @throws InjectorException if anything happen
* @throws java.lang.Throwable if anything happen
*/
@Nullable <T> T invokeParameter(Parameter parameter, Object... injectorArgs) throws Throwable;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ protected Boolean customHandle(Handler handler, Context context, LocalChannel ch
}

@Autowired(order = 1)
void parse(Context context, LocalChannel channel, @Ctx Scope parent, @Src("declaration") Snippet declaration) throws Exception {
public void parse(Context context, LocalChannel channel, @Ctx Scope parent, @Src("declaration") Snippet declaration) throws Exception {
BlockSubparser blockParser = channel.get("subparser", BlockSubparser.class);

Context delegatedContext = channel.allocated("blockContext", context.fork())
Expand Down Expand Up @@ -96,7 +96,7 @@ void parse(Context context, LocalChannel channel, @Ctx Scope parent, @Src("decla
}

@Autowired(order = 2)
void parseContent(@Channel Context blockContext, @Channel Block block, @Nullable @Src("body") Snippet body) throws Exception {
public void parseContent(@Channel Context blockContext, @Channel Block block, @Nullable @Src("body") Snippet body) throws Exception {
if (body != null) {
SCOPE_PARSER.parse(blockContext, block, body);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,31 @@
import org.panda_lang.framework.design.interpreter.parser.ContextParser;
import org.panda_lang.framework.design.interpreter.parser.ParserRepresentation;
import org.panda_lang.framework.language.interpreter.parser.pipeline.PandaParserRepresentation;
import org.panda_lang.utilities.inject.DependencyInjection;
import org.panda_lang.utilities.inject.Injector;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

final class BootstrapGenerator {

protected <T> ParserRepresentation<ContextParser<T>> generate(BootstrapInitializer<T> initializer, BootstrapContent content) {
List<BootstrapMethod> methods = initializer.layers.stream()
.map(BootstrapMethod::new)
.sorted(Comparator.comparingInt(BootstrapMethod::getOrder))
.collect(Collectors.toList());
private static final Injector INJECTOR = DependencyInjection.createInjector(new BootstrapInjectorController());

protected <T> ParserRepresentation<ContextParser<T>> generate(BootstrapInitializer<T> initializer, BootstrapContent content) {
List<BootstrapMethod> methods = new ArrayList<>(initializer.layers.size());

for (Method layer : initializer.layers) {
try {
methods.add(new BootstrapMethod(INJECTOR.forGeneratedMethod(layer)));
} catch (Exception e) {
e.printStackTrace();
throw new BootstrapException("Cannot generate bootstrap method", e);
}
}

methods.sort(Comparator.comparingInt(BootstrapMethod::getOrder));
content.getInitializer().initialize(content);

content.getHandler()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

public final class BootstrapInitializer<T> {

private static final BootstrapGenerator BOOTSTRAP_GENERATOR = new BootstrapGenerator();

protected String name;
protected Object instance;

Expand Down Expand Up @@ -130,7 +132,7 @@ protected ParserRepresentation<ContextParser<T>> build(Context context) {
initializer = (ctx, channel) -> {};
}

return new BootstrapGenerator().generate(this, new BootstrapContentImpl(name, instance, context, handler, initializer, pattern));
return BOOTSTRAP_GENERATOR.generate(this, new BootstrapContentImpl(name, instance, context, handler, initializer, pattern));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.panda_lang.panda.language.interpreter.parser.context.annotations.Ctx;
import org.panda_lang.panda.language.interpreter.parser.context.annotations.Channel;
import org.panda_lang.panda.language.interpreter.parser.context.annotations.Src;
import org.panda_lang.utilities.commons.ObjectUtils;
import org.panda_lang.utilities.commons.StringUtils;
import org.panda_lang.utilities.inject.InjectorController;
import org.panda_lang.utilities.inject.InjectorResources;
Expand All @@ -35,31 +36,25 @@

final class BootstrapInjectorController implements InjectorController {

private final Context context;

BootstrapInjectorController(Context context) {
this.context = context;
}

@Override
public void initialize(InjectorResources resources) {
resources.on(Context.class).assignInstance(() -> context);
resources.on(LocalChannel.class).assignInstance(() -> context.getComponent(Components.CHANNEL));
resources.on(Context.class).assignHandler((parameter, annotation, injectorArgs) -> context(injectorArgs));
resources.on(LocalChannel.class).assignHandler((parameter, annotation, injectorArgs) -> channel(context(injectorArgs)));

resources.annotatedWith(Ctx.class).assignHandler((required, annotation, injectorArgs) -> {
return findComponent(annotation, required);
return findComponent(context(injectorArgs), annotation, required);
});

resources.annotatedWith(Channel.class).assignHandler((required, annotation, injectorArgs) -> {
return findInChannel(annotation, required);
return findInChannel(context(injectorArgs), annotation, required);
});

resources.annotatedWith(Src.class).assignHandler((required, annotation, injectorArgs) -> {
return findSource(annotation, required);
return findSource(context(injectorArgs), annotation, required);
});
}

private @Nullable Object findComponent(Ctx ctx, Parameter required) {
private @Nullable Object findComponent(Context context, Ctx ctx, Parameter required) {
return context.getComponents().entrySet().stream()
.filter(entry -> {
String value = ctx.value();
Expand All @@ -77,14 +72,14 @@ public void initialize(InjectorResources resources) {
.orElse(null);
}

private @Nullable Object findSource(Src src, Parameter required) {
LocalChannel channel = getChannel();
private @Nullable Object findSource(Context context, Src src, Parameter required) {
LocalChannel channel = channel(context);

if (!channel.contains(Mappings.class)) {
throw new BootstrapException("Pattern mappings are not defined for @Redactor");
}

Mappings redactor = getChannel().get(Mappings.class);
Mappings redactor = channel.get(Mappings.class);
Object value = redactor.get(src.value()).getOrNull();
Class<?> requiredType = required.getType();

Expand All @@ -99,9 +94,9 @@ public void initialize(InjectorResources resources) {
return value;
}

private @Nullable Object findInChannel(Channel channelAnnotation, Parameter required) {
private @Nullable Object findInChannel(Context context, Channel channelAnnotation, Parameter required) {
String name = channelAnnotation.value();
LocalChannel channel = getChannel();
LocalChannel channel = channel(context);

if (!StringUtils.isEmpty(name)) {
return channel.get(name);
Expand All @@ -110,7 +105,11 @@ public void initialize(InjectorResources resources) {
return channel.get(required.getType());
}

private LocalChannel getChannel() {
private Context context(Object[] injectorArgs) {
return Objects.requireNonNull(ObjectUtils.cast(injectorArgs[0]));
}

private LocalChannel channel(Context context) {
return context.getComponent(Components.CHANNEL);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,19 @@
package org.panda_lang.panda.language.interpreter.parser.context;

import org.panda_lang.panda.language.interpreter.parser.context.annotations.Autowired;

import java.lang.reflect.Method;
import org.panda_lang.utilities.inject.GeneratedMethodInjector;

final class BootstrapMethod {

protected final Method method;
protected final Autowired autowired;
protected final GeneratedMethodInjector generatedMethod;

BootstrapMethod(Method method) {
this.method = method;
this.autowired = method.getAnnotation(Autowired.class);
BootstrapMethod(GeneratedMethodInjector generatedMethod) {
this.generatedMethod = generatedMethod;
this.autowired = generatedMethod.getMethod().getAnnotation(Autowired.class);

if (autowired == null) {
throw new BootstrapException("Method " + method.getName() + " is not annotated by @Autowired");
throw new BootstrapException("Method " + generatedMethod.getMethod().getName() + " is not annotated by @Autowired");
}
}

Expand All @@ -46,13 +45,17 @@ protected String getCycle() {
return autowired.cycle();
}

protected Method getMethod() {
return method;
protected GeneratedMethodInjector getGeneratedMethod() {
return generatedMethod;
}

public String getName() {
return generatedMethod.getMethod().getName();
}

@Override
public String toString() {
return "Bootstrap Method: " + method + " / " + autowired;
return "Bootstrap Method: " + generatedMethod.getMethod() + " / " + autowired;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@
import org.panda_lang.framework.design.interpreter.parser.generation.GenerationCycle;
import org.panda_lang.framework.design.interpreter.parser.generation.GenerationPhase;
import org.panda_lang.framework.design.interpreter.parser.generation.GenerationTask;
import org.panda_lang.utilities.inject.DependencyInjection;
import org.panda_lang.utilities.inject.Injector;
import org.panda_lang.utilities.inject.InjectorController;

import java.lang.reflect.InvocationTargetException;
import java.util.Stack;
Expand All @@ -44,19 +41,14 @@ final class BootstrapTaskScheduler<T> {
this.methods = methods;
}

protected T schedule(Context context) throws Exception {
return schedule(context, new BootstrapInjectorController(context));
}

private @Nullable T schedule(Context context, InjectorController controller) throws Exception {
Injector injector = DependencyInjection.createInjector(controller);
protected @Nullable T schedule(Context context) throws Exception {
int currentOrder = methods.peek().getOrder();

while (hasNext(currentOrder)) {
BootstrapMethod currentMethod = methods.pop();
boolean last = !hasNext(currentOrder);

T value = delegateNext(context, controller, injector, currentMethod, last);
T value = delegateNext(context, currentMethod, last);

if (last) {
return value;
Expand All @@ -66,24 +58,24 @@ protected T schedule(Context context) throws Exception {
return null;
}

private T delegateNext(Context context, InjectorController controller, Injector injector, BootstrapMethod method, boolean last) throws Exception {
private T delegateNext(Context context, BootstrapMethod method, boolean last) throws Exception {
GenerationTask<T> callback = (cycle, delegatedContext) -> {
T value;

try {
value = injector.invokeMethod(method.getMethod(), content.getInstance());
value = method.getGeneratedMethod().invoke(content.getInstance(), delegatedContext);
} catch (InvocationTargetException e) {
if (e.getTargetException() instanceof Exception) {
throw (Exception) e.getTargetException();
}

throw new BootstrapException("Cannot execute " + method.getMethod() + " -> " + e.getTargetException().getMessage(), e.getTargetException());
throw new BootstrapException("Cannot execute " + method.getName() + " -> " + e.getTargetException().getMessage(), e.getTargetException());
} catch (Throwable e) {
throw new BootstrapException("Cannot execute " + method.getMethod() + " -> " + e.getMessage(), e);
throw new BootstrapException("Cannot execute " + method.getName() + " -> " + e.getMessage(), e);
}

if (last && !methods.isEmpty()) {
schedule(delegatedContext.fork(), controller);
schedule(delegatedContext.fork());
}

return value;
Expand Down
Loading

0 comments on commit 0b9ae3b

Please sign in to comment.