Skip to content
This repository was archived by the owner on Aug 31, 2019. It is now read-only.
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
7 changes: 6 additions & 1 deletion src/main/java/tc/oc/minecraft/api/command/CommandSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@

import net.md_5.bungee.api.chat.BaseComponent;
import tc.oc.minecraft.api.permissions.Permissible;
import tc.oc.reference.Handle;
import tc.oc.reference.Handleable;

public interface CommandSender extends Permissible {
public interface CommandSender extends Permissible, Handleable {

@Override
Handle<? extends CommandSender> handle();

/**
* Get the unique name of this command sender.
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/tc/oc/minecraft/api/entity/Player.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import net.md_5.bungee.api.ChatMessageType;
import net.md_5.bungee.api.chat.BaseComponent;
import tc.oc.minecraft.api.command.CommandSender;
import tc.oc.reference.Handle;

public interface Player extends CommandSender {

@Override
Handle<? extends Player> handle();

/**
* Gets this player's display name.
*
Expand Down Expand Up @@ -56,4 +60,6 @@ public interface Player extends CommandSender {
* Get the Minecraft protocol version in use by this player's connection
*/
int getProtocolVersion();


}
8 changes: 7 additions & 1 deletion src/main/java/tc/oc/minecraft/api/plugin/Plugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
import tc.oc.exception.ExceptionHandler;
import tc.oc.minecraft.api.server.Server;
import tc.oc.minecraft.api.logging.Loggable;
import tc.oc.reference.Handle;
import tc.oc.reference.Handleable;

public interface Plugin extends Loggable, Handleable {

@Override
Handle<? extends Plugin> handle();

public interface Plugin extends Loggable {
/**
* Return metadata about this plugin
*/
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/tc/oc/minecraft/api/server/LocalServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
import tc.oc.minecraft.api.command.ConsoleCommandSender;
import tc.oc.minecraft.api.logging.Loggable;
import tc.oc.minecraft.api.plugin.PluginFinder;
import tc.oc.reference.Handle;

/**
* The local server i.e. the one hosting plugins
*/
public interface LocalServer extends Loggable, Server {

@Override
Handle<? extends LocalServer> handle();

PluginFinder getPluginFinder();

ConsoleCommandSender getConsoleSender();
Expand Down
7 changes: 6 additions & 1 deletion src/main/java/tc/oc/minecraft/api/server/Server.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
import java.util.UUID;

import tc.oc.minecraft.api.entity.Player;
import tc.oc.reference.Handle;
import tc.oc.reference.Handleable;

/**
* A Minecraft server or proxy, local or remote
*/
public interface Server {
public interface Server extends Handleable {

@Override
Handle<? extends Server> handle();

/**
* Return all players currently connected.
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/tc/oc/proxy/Proxies.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package tc.oc.proxy;

import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.function.Supplier;

public class Proxies {

public static <T> T forwarding(Class<T> type, Supplier<? extends T> supplier) {
return forwarding(type.getClassLoader(), type, supplier);
}

public static <T> T forwarding(ClassLoader loader, Class<T> type, Supplier<? extends T> supplier) {
return (T) Proxy.newProxyInstance(
loader,
new Class<?>[]{type},
(proxy, method, args) -> method.invoke(supplier.get(), args)
);
}

public static <T> T forwarding(ClassLoader loader, Class<T> type, Supplier<? extends T> supplier, Map<Class<?>, ?> extensions) {
final Class[] inters = new Class[extensions.size() + 1];
int i = 0;
for(Class inter : extensions.keySet()) {
inters[i++] = inter;
}
inters[i] = type;

return (T) Proxy.newProxyInstance(
loader,
inters,
(proxy, method, args) -> {
for(Class<?> inter : extensions.keySet()) {
if(method.getDeclaringClass().isAssignableFrom(inter)) {
return method.invoke(extensions.get(inter), args);
}
}
return method.invoke(supplier.get(), args);
}
);
}
}
150 changes: 150 additions & 0 deletions src/main/java/tc/oc/proxy/ProxyBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package tc.oc.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

public class ProxyBuilder {

private static final List<Method> IDENTITY_METHODS;

static {
try {
IDENTITY_METHODS = Arrays.asList(
Object.class.getDeclaredMethod("equals", Object.class),
Object.class.getDeclaredMethod("hashCode")
);

} catch(NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}

private ClassLoader loader = null;
private final Map<Method, InvocationHandler> methodHandlers = new LinkedHashMap<>();
private final Map<Class<?>, InvocationHandler> typeHandlers = new LinkedHashMap<>();

public ProxyBuilder loadFrom(ClassLoader loader) {
this.loader = loader;
return this;
}

public HandlerBuilder delegate(Method method) {
return new HandlerBuilder<>(to -> methodHandlers.put(method, to));
}

public HandlerBuilder delegate(Iterable<Method> methods) {
return new HandlerBuilder<>(to -> {
for(Method method : methods) {
methodHandlers.put(method, to);
}
});
}

public <T> HandlerBuilder<T> delegate(Class<T> type) {
return new HandlerBuilder<>(to -> typeHandlers.put(type, to));
}

public HandlerBuilder delegateIdentity() {
return delegate(IDENTITY_METHODS);
}

private void validate() {
for(Class<?> type : typeHandlers.keySet()) {
if(!type.isInterface() && !type.equals(Object.class)) {
throw new IllegalArgumentException("Cannot proxy non-interface type " + type.getName());
}
}
for(Method method : methodHandlers.keySet()) {
if(typeHandlers.keySet().stream().noneMatch(method.getDeclaringClass()::isAssignableFrom)) {
throw new IllegalArgumentException("Method " + method.getName() + " is not present in any implemented interfaces");
}
}
}

public Object newProxyInstance() {
validate();
return Proxy.newProxyInstance(
loader != null ? loader : Thread.currentThread().getContextClassLoader(),
typeHandlers.keySet().toArray(new Class<?>[typeHandlers.size()]),
new Invoker(methodHandlers, typeHandlers)
);
}

public class HandlerBuilder<T> {
private Consumer<InvocationHandler> consumer;

private HandlerBuilder(Consumer<InvocationHandler> consumer) {
this.consumer = consumer;
}

public ProxyBuilder to(InvocationHandler handler) {
if(consumer == null) {
throw new IllegalStateException();
}
consumer.accept(handler);
consumer = null;
return ProxyBuilder.this;
}

public ProxyBuilder toInstance(T to) {
return to((proxy, m, args) -> m.invoke(to, args));
}

public ProxyBuilder toSupplier(Supplier<T> to) {
return to((proxy, method, args) -> method.invoke(to.get(), args));
}
}

private static class Invoker implements InvocationHandler {

private final LoadingCache<Method, InvocationHandler> handlers;

private Invoker(Map<Method, InvocationHandler> methods, Map<Class<?>, InvocationHandler> types) {
this.handlers = CacheBuilder.newBuilder().build(new CacheLoader<Method, InvocationHandler>() {
@Override
public InvocationHandler load(Method method) throws Exception {
// Look for an exact method
InvocationHandler handler = methods.get(method);
if(handler != null) return handler;

// Look for the exact declaring interface of the method
// If Object is delegated, that will be caught here
final Class<?> decl = method.getDeclaringClass();
handler = types.get(decl);
if(handler != null) return handler;

// Any Object methods not handled above are delegated to the Invoker itself
if(decl.equals(Object.class)) {
return (proxy, method1, args) -> method1.invoke(Invoker.this, args);
}

// If the method is not from Object, look for an interface that inherits it
for(Map.Entry<Class<?>, InvocationHandler> entry : types.entrySet()) {
if(decl.isAssignableFrom(entry.getKey())) {
return entry.getValue();
}
}

// Give up ¯\_(ツ)_/¯
throw new NoSuchMethodError(method.getName());
}
});
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return handlers.get(method).invoke(proxy, method, args);
}
}
}
Loading