ProtocolLib/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java

284 lines
8.6 KiB
Java

/**
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. Copyright (C) 2015 dmulloy2
* <p>
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
* version.
* <p>
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
* <p>
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
import io.netty.channel.Channel;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Represents an injector factory.
* <p>
* Note that the factory will return {@link EmptyInjector} when the factory is closed.
*
* @author Kristian
*/
public class InjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
// bukkit stuff
private final Plugin plugin;
private final Server server;
// protocol lib stuff
private final ErrorReporter errorReporter;
// state of the factory
private boolean closed;
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
this.plugin = plugin;
this.server = server;
this.errorReporter = errorReporter;
}
/**
* Retrieve the main plugin associated with this injection factory.
*
* @return The main plugin.
*/
public Plugin getPlugin() {
return this.plugin;
}
/**
* Construct or retrieve a channel injector from an existing Bukkit player.
*
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player, ChannelListener listener) {
if (this.closed) {
return new EmptyInjector(player);
}
// try to get the injector using the player reference first
Injector injector = this.playerLookup.get(player);
if (injector == null) {
injector = this.getTemporaryInjector(player);
}
// check if we found an injector
if (injector != null && !injector.isClosed()) {
return injector;
}
// check if a network manager is present, if not maybe we cached the player temporarily
Object networkManager = MinecraftFields.getNetworkManager(player);
if (networkManager == null) {
return this.fromName(player.getName(), player);
}
// get the channel of the player and check if we already hooked into it
Channel channel = FuzzyReflection.getFieldValue(networkManager, Channel.class, true);
injector = NettyChannelInjector.findInjector(channel);
if (injector != null) {
// check if the new player is not the old one, this saves us a bit when many calls to the method are made
if (injector.getPlayer() != player || !this.playerLookup.containsKey(player)) {
this.playerLookup.remove(injector.getPlayer());
this.cacheInjector(player, injector);
// re-set the player of the injection
injector.setPlayer(player);
}
} else {
// construct a new injector as it seems like we have none yet
injector = new NettyChannelInjector(this.server, networkManager, channel, listener, this, this.errorReporter);
this.cacheInjector(player, injector);
}
// definitely not null
return injector;
}
/**
* Retrieve a cached injector from a name.
* <p>
* The injector may be NULL if the plugin has been reloaded during a player login.
*
* @param name - the name.
* @param player - the player.
* @return The cached injector, or a closed injector if it could not be found.
*/
public Injector fromName(String name, Player player) {
if (this.closed) {
return new EmptyInjector(player);
}
// check if we have a player with that name cached
Injector injector = this.nameLookup.get(name);
if (injector != null) {
injector.setPlayer(player);
return injector;
}
return new EmptyInjector(player);
}
/**
* Construct a new channel injector for the given channel.
*
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (this.closed) {
return EmptyInjector.WITHOUT_PLAYER;
}
Object netManager = this.findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
NettyChannelInjector injector = new NettyChannelInjector(
this.server,
netManager,
channel,
listener,
this,
this.errorReporter);
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
return injector;
}
/**
* Invalidate a cached injector.
*
* @param player - the associated player.
* @return The cached injector, or NULL if nothing was cached.
*/
public Injector invalidate(Player player, String name) {
Injector injector = null;
// try the name first, more unsafe but works 99% of the time
if (name != null) {
injector = this.nameLookup.remove(name);
}
// if we have a player then use that as the safe removal way
if (player != null) {
injector = this.playerLookup.remove(player);
}
return injector;
}
/**
* Cache an injector by player.
*
* @param player - the player.
* @param injector - the injector to cache.
* @return The previously cached injector.
*/
public Injector cacheInjector(Player player, Injector injector) {
this.nameLookup.put(player.getName(), injector);
return this.playerLookup.put(player, injector);
}
/**
* Cache an injector by name alone.
*
* @param name - the name to lookup.
* @param injector - the injector.
* @return The cached injector.
*/
public Injector cacheInjector(String name, Injector injector) {
return this.nameLookup.put(name, injector);
}
/**
* Retrieve the associated channel injector.
*
* @param player - the temporary player, or normal Bukkit player.
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelMinimalInjector) {
return ((NettyChannelMinimalInjector) injector).getInjector();
}
return null;
}
/**
* Find the network manager in a channel's pipeline.
*
* @param channel - the channel.
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
MinecraftReflection.getNetworkManagerClass());
if (networkManager != null) {
return networkManager;
}
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
}
/**
* Determine if the factory is closed.
* <p>
* If it is, all new injectors will be closed by default.
*
* @return TRUE if it is closed, FALSE otherwise.
*/
public boolean isClosed() {
return this.closed;
}
/**
* Close all injectors created by this factory, and cease the creation of new injections.
*/
public void close() {
if (!this.closed) {
this.closed = true;
// Close everything
for (Injector injector : this.playerLookup.values()) {
injector.close();
}
for (Injector injector : this.nameLookup.values()) {
injector.close();
}
}
}
}